package main import ( "context" "database/sql" "fmt" "strings" ) // mysqlCol returns a quoted column name if present, otherwise a SQL literal/expression fallback. func mysqlCol(ctx context.Context, db *sql.DB, table, column, fallbackExpr string) string { if mysqlColumnExists(ctx, db, table, column) { q, err := quoteMySQLIdent(column) if err != nil { return fallbackExpr } return q } return fallbackExpr } // mysqlCoalesce returns COALESCE(column, fallback) when column exists, else fallback alone. func mysqlCoalesce(ctx context.Context, db *sql.DB, table, column, fallbackExpr string) string { if mysqlColumnExists(ctx, db, table, column) { q, err := quoteMySQLIdent(column) if err != nil { return fallbackExpr } return fmt.Sprintf("COALESCE(%s, %s)", q, fallbackExpr) } return fallbackExpr } // mysqlSelectList builds "SELECT a, b, ..." from expressions (already resolved). func mysqlSelectList(exprs ...string) string { return "SELECT " + strings.Join(exprs, ", ") } // scanIntish scans MySQL INT/DECIMAL/string numeric values into an int. func scanIntish(v any) int { switch x := v.(type) { case int64: return int(x) case int32: return int(x) case float64: return int(x) case []byte: var n float64 if _, err := fmt.Sscanf(string(x), "%f", &n); err == nil { return int(n) } case string: var n float64 if _, err := fmt.Sscanf(x, "%f", &n); err == nil { return int(n) } } return 0 } // queryFirstOK tries queries in order until one succeeds (for column-shape fallbacks). func queryFirstOK(ctx context.Context, db *sql.DB, queries ...string) (*sql.Rows, error) { var last error for _, q := range queries { rows, err := db.QueryContext(ctx, q) if err == nil { return rows, nil } last = err } if last == nil { return nil, fmt.Errorf("no queries provided") } return nil, last }