Files
egutierrez 47235e702c feat: abstracción DB multi-engine — CRUD genérico y openers para SQLite, Postgres, ClickHouse, DuckDB
Funciones Go con interfaz unificada para operaciones DB: open, close, create_table, exec, query, insert_row, insert_batch.
Openers específicos por engine. Tipo DBConfig para configuración común.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-01 20:55:17 +02:00

80 lines
2.0 KiB
Go

package infra
import (
"database/sql"
"fmt"
"strconv"
)
// DBQuery executes a SELECT query and returns the results as a slice of maps.
// Each map key is the column name; values are converted to native Go types:
// int64, float64, bool, string, []byte, or nil for NULLs.
func DBQuery(db *sql.DB, query string, args ...any) ([]map[string]any, error) {
rows, err := db.Query(query, args...)
if err != nil {
return nil, fmt.Errorf("db_query: %w", err)
}
defer rows.Close()
cols, err := rows.Columns()
if err != nil {
return nil, fmt.Errorf("db_query: columns: %w", err)
}
colTypes, err := rows.ColumnTypes()
if err != nil {
return nil, fmt.Errorf("db_query: column types: %w", err)
}
var results []map[string]any
for rows.Next() {
// Use RawBytes so we can inspect the raw value before converting.
raw := make([]sql.RawBytes, len(cols))
ptrs := make([]any, len(cols))
for i := range raw {
ptrs[i] = &raw[i]
}
if err := rows.Scan(ptrs...); err != nil {
return nil, fmt.Errorf("db_query: scan: %w", err)
}
row := make(map[string]any, len(cols))
for i, col := range cols {
if raw[i] == nil {
row[col] = nil
continue
}
row[col] = convertRaw(raw[i], colTypes[i].DatabaseTypeName())
}
results = append(results, row)
}
if err := rows.Err(); err != nil {
return nil, fmt.Errorf("db_query: rows: %w", err)
}
return results, nil
}
// convertRaw attempts to convert a raw SQL byte slice into a native Go type
// based on the database column type name hint.
func convertRaw(b sql.RawBytes, dbType string) any {
s := string(b)
switch dbType {
case "INTEGER", "INT", "BIGINT", "SMALLINT", "TINYINT", "INT2", "INT4", "INT8":
if v, err := strconv.ParseInt(s, 10, 64); err == nil {
return v
}
case "REAL", "FLOAT", "DOUBLE", "NUMERIC", "DECIMAL":
if v, err := strconv.ParseFloat(s, 64); err == nil {
return v
}
case "BOOLEAN", "BOOL":
if v, err := strconv.ParseBool(s); err == nil {
return v
}
case "BLOB":
cp := make([]byte, len(b))
copy(cp, b)
return cp
}
return s
}