47235e702c
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>
80 lines
2.0 KiB
Go
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
|
|
}
|