fix(infra): gradle_run detecta android-sdk — issue 0076 #2
@@ -0,0 +1,25 @@
|
||||
package infra
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
|
||||
_ "github.com/ClickHouse/clickhouse-go/v2"
|
||||
)
|
||||
|
||||
// ClickHouseOpen connects to a ClickHouse server and returns a *sql.DB.
|
||||
// Constructs a DSN of the form:
|
||||
//
|
||||
// clickhouse://user:password@host:port/database
|
||||
func ClickHouseOpen(host string, port int, user, password, database string) (*sql.DB, error) {
|
||||
dsn := fmt.Sprintf("clickhouse://%s:%s@%s:%d/%s", user, password, host, port, database)
|
||||
db, err := sql.Open("clickhouse", dsn)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("clickhouse_open: open: %w", err)
|
||||
}
|
||||
if err := db.Ping(); err != nil {
|
||||
db.Close()
|
||||
return nil, fmt.Errorf("clickhouse_open: ping %s:%d/%s: %w", host, port, database, err)
|
||||
}
|
||||
return db, nil
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
---
|
||||
name: clickhouse_open
|
||||
kind: function
|
||||
lang: go
|
||||
domain: infra
|
||||
version: "1.0.0"
|
||||
purity: impure
|
||||
signature: "func ClickHouseOpen(host string, port int, user, password, database string) (*sql.DB, error)"
|
||||
description: "Conecta a ClickHouse construyendo DSN clickhouse://user:pass@host:port/database."
|
||||
tags: [database, clickhouse, connection, sql, olap]
|
||||
uses_functions: []
|
||||
uses_types: [db_config_go_infra]
|
||||
returns: []
|
||||
returns_optional: false
|
||||
error_type: "error_go_core"
|
||||
imports: ["database/sql", "github.com/ClickHouse/clickhouse-go/v2"]
|
||||
tested: false
|
||||
tests: []
|
||||
test_file_path: ""
|
||||
file_path: "functions/infra/clickhouse_open.go"
|
||||
---
|
||||
|
||||
## Ejemplo
|
||||
|
||||
```go
|
||||
db, err := ClickHouseOpen("localhost", 9000, "default", "", "analytics")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer DBClose(db)
|
||||
|
||||
rows, err := DBQuery(db, "SELECT event, count() FROM events GROUP BY event")
|
||||
```
|
||||
|
||||
## Notas
|
||||
|
||||
Usa el driver `github.com/ClickHouse/clickhouse-go/v2` registrado como "clickhouse". Puerto por defecto de ClickHouse es 9000 (nativo) o 8123 (HTTP). Hace ping al abrir para verificar conectividad.
|
||||
@@ -0,0 +1,18 @@
|
||||
package infra
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// DBClose closes the database connection. Wraps db.Close() for composability
|
||||
// in pipelines that manage *sql.DB lifecycle explicitly.
|
||||
func DBClose(db *sql.DB) error {
|
||||
if db == nil {
|
||||
return fmt.Errorf("db_close: db is nil")
|
||||
}
|
||||
if err := db.Close(); err != nil {
|
||||
return fmt.Errorf("db_close: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
---
|
||||
name: db_close
|
||||
kind: function
|
||||
lang: go
|
||||
domain: infra
|
||||
version: "1.0.0"
|
||||
purity: impure
|
||||
signature: "func DBClose(db *sql.DB) error"
|
||||
description: "Cierra la conexion a la base de datos. Wrapper sobre db.Close() para composabilidad en pipelines que gestionan el ciclo de vida de *sql.DB explicitamente."
|
||||
tags: [database, sql, close, lifecycle]
|
||||
uses_functions: []
|
||||
uses_types: []
|
||||
returns: []
|
||||
returns_optional: false
|
||||
error_type: "error_go_core"
|
||||
imports: ["database/sql"]
|
||||
tested: false
|
||||
tests: []
|
||||
test_file_path: ""
|
||||
file_path: "functions/infra/db_close.go"
|
||||
---
|
||||
|
||||
## Ejemplo
|
||||
|
||||
```go
|
||||
db, err := SQLiteOpen("/data/app.db")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer DBClose(db)
|
||||
```
|
||||
|
||||
## Notas
|
||||
|
||||
Retorna error si db es nil. En la mayoria de los casos se usa con `defer`. Existe como funcion del registry para que los pipelines puedan referenciarla en `uses_functions` y modelar el ciclo de vida completo de la conexion.
|
||||
@@ -0,0 +1,8 @@
|
||||
package infra
|
||||
|
||||
// DBConfig holds connection parameters for any supported database.
|
||||
type DBConfig struct {
|
||||
Driver string // "sqlite", "duckdb", "postgres", "clickhouse"
|
||||
DSN string // Data source name / connection string
|
||||
Opts map[string]string // Driver-specific options (optional)
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package infra
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var validIdentifier = regexp.MustCompile(`^[a-zA-Z_][a-zA-Z0-9_]*$`)
|
||||
|
||||
// DBCreateTable executes CREATE TABLE IF NOT EXISTS for the given table with
|
||||
// the provided column definitions. Each element of columns should be a full
|
||||
// SQL column definition, e.g. "id INTEGER PRIMARY KEY" or "name TEXT NOT NULL".
|
||||
// Returns an error if the table name contains invalid characters.
|
||||
func DBCreateTable(db *sql.DB, table string, columns []string) error {
|
||||
if !validIdentifier.MatchString(table) {
|
||||
return fmt.Errorf("db_create_table: invalid table name %q (only alphanumeric and underscore allowed)", table)
|
||||
}
|
||||
if len(columns) == 0 {
|
||||
return fmt.Errorf("db_create_table: at least one column definition required")
|
||||
}
|
||||
query := fmt.Sprintf(
|
||||
"CREATE TABLE IF NOT EXISTS %s (%s)",
|
||||
table,
|
||||
strings.Join(columns, ", "),
|
||||
)
|
||||
if _, err := db.Exec(query); err != nil {
|
||||
return fmt.Errorf("db_create_table %q: %w", table, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
---
|
||||
name: db_create_table
|
||||
kind: function
|
||||
lang: go
|
||||
domain: infra
|
||||
version: "1.0.0"
|
||||
purity: impure
|
||||
signature: "func DBCreateTable(db *sql.DB, table string, columns []string) error"
|
||||
description: "Ejecuta CREATE TABLE IF NOT EXISTS con las definiciones de columnas dadas. Valida que el nombre de tabla sea un identificador SQL seguro."
|
||||
tags: [database, sql, ddl, create, table, schema]
|
||||
uses_functions: []
|
||||
uses_types: []
|
||||
returns: []
|
||||
returns_optional: false
|
||||
error_type: "error_go_core"
|
||||
imports: ["database/sql", "regexp", "strings"]
|
||||
tested: false
|
||||
tests: []
|
||||
test_file_path: ""
|
||||
file_path: "functions/infra/db_create_table.go"
|
||||
---
|
||||
|
||||
## Ejemplo
|
||||
|
||||
```go
|
||||
err := DBCreateTable(db, "events", []string{
|
||||
"id INTEGER PRIMARY KEY AUTOINCREMENT",
|
||||
"name TEXT NOT NULL",
|
||||
"ts INTEGER NOT NULL",
|
||||
"payload TEXT",
|
||||
})
|
||||
```
|
||||
|
||||
## Notas
|
||||
|
||||
`columns` son definiciones SQL completas incluyendo nombre, tipo y constraints. Usa `CREATE TABLE IF NOT EXISTS` para ser idempotente. Valida el nombre de tabla con regex `^[a-zA-Z_][a-zA-Z0-9_]*$` para prevenir SQL injection. Las definiciones de columna no se sanitizan — son responsabilidad del llamador.
|
||||
@@ -0,0 +1,22 @@
|
||||
package infra
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// DBExec executes a non-SELECT statement (INSERT, UPDATE, DELETE, DDL) and
|
||||
// returns the number of rows affected. For statements that don't return rows
|
||||
// affected (e.g. DDL on some drivers), the count may be 0.
|
||||
func DBExec(db *sql.DB, query string, args ...any) (int64, error) {
|
||||
result, err := db.Exec(query, args...)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("db_exec: %w", err)
|
||||
}
|
||||
n, err := result.RowsAffected()
|
||||
if err != nil {
|
||||
// Some drivers don't support RowsAffected; treat as 0.
|
||||
return 0, nil
|
||||
}
|
||||
return n, nil
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
---
|
||||
name: db_exec
|
||||
kind: function
|
||||
lang: go
|
||||
domain: infra
|
||||
version: "1.0.0"
|
||||
purity: impure
|
||||
signature: "func DBExec(db *sql.DB, query string, args ...any) (int64, error)"
|
||||
description: "Ejecuta un statement no-SELECT (INSERT, UPDATE, DELETE, DDL) y retorna el numero de filas afectadas."
|
||||
tags: [database, sql, exec, insert, update, delete, ddl]
|
||||
uses_functions: []
|
||||
uses_types: []
|
||||
returns: []
|
||||
returns_optional: false
|
||||
error_type: "error_go_core"
|
||||
imports: ["database/sql"]
|
||||
tested: false
|
||||
tests: []
|
||||
test_file_path: ""
|
||||
file_path: "functions/infra/db_exec.go"
|
||||
---
|
||||
|
||||
## Ejemplo
|
||||
|
||||
```go
|
||||
n, err := DBExec(db, "UPDATE users SET active = ? WHERE last_login < ?", false, cutoff)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Printf("desactivados: %d usuarios\n", n)
|
||||
```
|
||||
|
||||
## Notas
|
||||
|
||||
Agnóstica al driver. Para DDL algunos drivers retornan 0 en RowsAffected — esto es normal. Para INSERT con last_insert_id usar `DBInsertRow` que retorna ese valor. Para multiples filas en una transaccion usar `DBInsertBatch`.
|
||||
@@ -0,0 +1,70 @@
|
||||
package infra
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// DBInsertBatch inserts multiple rows into a table using a prepared statement
|
||||
// inside a transaction. columns must match the order of values in each row.
|
||||
// Returns the total number of rows affected.
|
||||
// Column and table names are validated to contain only safe identifier chars.
|
||||
func DBInsertBatch(db *sql.DB, table string, columns []string, rows [][]any) (int64, error) {
|
||||
if !validIdentifier.MatchString(table) {
|
||||
return 0, fmt.Errorf("db_insert_batch: invalid table name %q", table)
|
||||
}
|
||||
if len(columns) == 0 {
|
||||
return 0, fmt.Errorf("db_insert_batch: columns must not be empty")
|
||||
}
|
||||
if len(rows) == 0 {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
for _, col := range columns {
|
||||
if !validIdentifier.MatchString(col) {
|
||||
return 0, fmt.Errorf("db_insert_batch: invalid column name %q", col)
|
||||
}
|
||||
}
|
||||
|
||||
placeholders := make([]string, len(columns))
|
||||
for i := range columns {
|
||||
placeholders[i] = "?"
|
||||
}
|
||||
query := fmt.Sprintf(
|
||||
"INSERT INTO %s (%s) VALUES (%s)",
|
||||
table,
|
||||
strings.Join(columns, ", "),
|
||||
strings.Join(placeholders, ", "),
|
||||
)
|
||||
|
||||
tx, err := db.Begin()
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("db_insert_batch: begin tx: %w", err)
|
||||
}
|
||||
defer tx.Rollback() //nolint:errcheck
|
||||
|
||||
stmt, err := tx.Prepare(query)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("db_insert_batch: prepare: %w", err)
|
||||
}
|
||||
defer stmt.Close()
|
||||
|
||||
var total int64
|
||||
for i, row := range rows {
|
||||
if len(row) != len(columns) {
|
||||
return 0, fmt.Errorf("db_insert_batch: row %d has %d values, expected %d", i, len(row), len(columns))
|
||||
}
|
||||
result, err := stmt.Exec(row...)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("db_insert_batch: exec row %d: %w", i, err)
|
||||
}
|
||||
n, _ := result.RowsAffected()
|
||||
total += n
|
||||
}
|
||||
|
||||
if err := tx.Commit(); err != nil {
|
||||
return 0, fmt.Errorf("db_insert_batch: commit: %w", err)
|
||||
}
|
||||
return total, nil
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
---
|
||||
name: db_insert_batch
|
||||
kind: function
|
||||
lang: go
|
||||
domain: infra
|
||||
version: "1.0.0"
|
||||
purity: impure
|
||||
signature: "func DBInsertBatch(db *sql.DB, table string, columns []string, rows [][]any) (int64, error)"
|
||||
description: "Inserta multiples filas en una transaccion usando prepared statement. Retorna el total de filas afectadas. Mas eficiente que llamar DBInsertRow en un loop."
|
||||
tags: [database, sql, insert, batch, transaction, bulk]
|
||||
uses_functions: [db_insert_row_go_infra]
|
||||
uses_types: []
|
||||
returns: []
|
||||
returns_optional: false
|
||||
error_type: "error_go_core"
|
||||
imports: ["database/sql", "strings"]
|
||||
tested: false
|
||||
tests: []
|
||||
test_file_path: ""
|
||||
file_path: "functions/infra/db_insert_batch.go"
|
||||
---
|
||||
|
||||
## Ejemplo
|
||||
|
||||
```go
|
||||
cols := []string{"name", "score", "ts"}
|
||||
rows := [][]any{
|
||||
{"Alice", 95.5, 1700000000},
|
||||
{"Bob", 87.2, 1700000001},
|
||||
{"Carol", 91.0, 1700000002},
|
||||
}
|
||||
n, err := DBInsertBatch(db, "results", cols, rows)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Printf("insertadas: %d filas\n", n)
|
||||
```
|
||||
|
||||
## Notas
|
||||
|
||||
Usa `tx.Prepare()` + `stmt.Exec()` en un loop dentro de una transaccion. El rollback es automatico si alguna fila falla. Valida tabla y columnas con regex. Cada fila debe tener exactamente `len(columns)` valores — retorna error descriptivo si no coincide.
|
||||
@@ -0,0 +1,54 @@
|
||||
package infra
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// DBInsertRow generates and executes a single-row INSERT from a map of
|
||||
// column→value pairs. Returns the last insert ID reported by the driver.
|
||||
// Column and table names are validated to contain only safe identifier chars.
|
||||
func DBInsertRow(db *sql.DB, table string, row map[string]any) (int64, error) {
|
||||
if !validIdentifier.MatchString(table) {
|
||||
return 0, fmt.Errorf("db_insert_row: invalid table name %q", table)
|
||||
}
|
||||
if len(row) == 0 {
|
||||
return 0, fmt.Errorf("db_insert_row: row map must not be empty")
|
||||
}
|
||||
|
||||
// Sort keys for deterministic query generation.
|
||||
cols := make([]string, 0, len(row))
|
||||
for col := range row {
|
||||
if !validIdentifier.MatchString(col) {
|
||||
return 0, fmt.Errorf("db_insert_row: invalid column name %q", col)
|
||||
}
|
||||
cols = append(cols, col)
|
||||
}
|
||||
sort.Strings(cols)
|
||||
|
||||
placeholders := make([]string, len(cols))
|
||||
values := make([]any, len(cols))
|
||||
for i, col := range cols {
|
||||
placeholders[i] = "?"
|
||||
values[i] = row[col]
|
||||
}
|
||||
|
||||
query := fmt.Sprintf(
|
||||
"INSERT INTO %s (%s) VALUES (%s)",
|
||||
table,
|
||||
strings.Join(cols, ", "),
|
||||
strings.Join(placeholders, ", "),
|
||||
)
|
||||
|
||||
result, err := db.Exec(query, values...)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("db_insert_row %q: %w", table, err)
|
||||
}
|
||||
id, err := result.LastInsertId()
|
||||
if err != nil {
|
||||
return 0, nil
|
||||
}
|
||||
return id, nil
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
---
|
||||
name: db_insert_row
|
||||
kind: function
|
||||
lang: go
|
||||
domain: infra
|
||||
version: "1.0.0"
|
||||
purity: impure
|
||||
signature: "func DBInsertRow(db *sql.DB, table string, row map[string]any) (int64, error)"
|
||||
description: "Genera y ejecuta un INSERT de una sola fila desde un map columna→valor. Retorna el last insert ID. Sanitiza nombres de tabla y columnas."
|
||||
tags: [database, sql, insert, row, dynamic]
|
||||
uses_functions: []
|
||||
uses_types: []
|
||||
returns: []
|
||||
returns_optional: false
|
||||
error_type: "error_go_core"
|
||||
imports: ["database/sql", "sort", "strings"]
|
||||
tested: false
|
||||
tests: []
|
||||
test_file_path: ""
|
||||
file_path: "functions/infra/db_insert_row.go"
|
||||
---
|
||||
|
||||
## Ejemplo
|
||||
|
||||
```go
|
||||
id, err := DBInsertRow(db, "users", map[string]any{
|
||||
"name": "Alice",
|
||||
"email": "alice@example.com",
|
||||
"active": true,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Printf("nuevo usuario ID: %d\n", id)
|
||||
```
|
||||
|
||||
## Notas
|
||||
|
||||
Las claves del map se ordenan alfabeticamente para generar queries deterministas. Valida tabla y columnas con regex `^[a-zA-Z_][a-zA-Z0-9_]*$`. Para insertar muchas filas usar `DBInsertBatch` que es mas eficiente. El last insert ID puede ser 0 en drivers que no lo soportan (ej: postgres — usar RETURNING en su lugar con `DBQuery`).
|
||||
@@ -0,0 +1,79 @@
|
||||
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
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
---
|
||||
name: db_query
|
||||
kind: function
|
||||
lang: go
|
||||
domain: infra
|
||||
version: "1.0.0"
|
||||
purity: impure
|
||||
signature: "func DBQuery(db *sql.DB, query string, args ...any) ([]map[string]any, error)"
|
||||
description: "Ejecuta un SELECT y retorna los resultados como slice de maps. Convierte valores a tipos nativos Go segun el tipo de columna reportado por el driver."
|
||||
tags: [database, sql, query, select, generic]
|
||||
uses_functions: []
|
||||
uses_types: []
|
||||
returns: []
|
||||
returns_optional: false
|
||||
error_type: "error_go_core"
|
||||
imports: ["database/sql"]
|
||||
tested: false
|
||||
tests: []
|
||||
test_file_path: ""
|
||||
file_path: "functions/infra/db_query.go"
|
||||
---
|
||||
|
||||
## Ejemplo
|
||||
|
||||
```go
|
||||
rows, err := DBQuery(db, "SELECT id, name, score FROM players WHERE active = ?", true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, row := range rows {
|
||||
fmt.Println(row["name"], row["score"])
|
||||
}
|
||||
```
|
||||
|
||||
## Notas
|
||||
|
||||
Agnóstica al driver — funciona con cualquier `*sql.DB` (sqlite, duckdb, postgres, clickhouse). Usa `sql.RawBytes` + `ColumnTypes()` para conversion dinamica. Convierte INTEGER→int64, FLOAT/REAL/DOUBLE→float64, BOOLEAN→bool, BLOB→[]byte, NULL→nil, resto→string. Para queries con muchos resultados considerar paginar con LIMIT/OFFSET.
|
||||
@@ -0,0 +1,27 @@
|
||||
package infra
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
|
||||
_ "github.com/marcboeker/go-duckdb"
|
||||
)
|
||||
|
||||
// DuckDBOpen opens (or creates) a DuckDB database file.
|
||||
// Pass an empty path or ":memory:" for an in-memory database.
|
||||
// Returns a ready-to-use *sql.DB or an error.
|
||||
func DuckDBOpen(path string) (*sql.DB, error) {
|
||||
dsn := path
|
||||
if dsn == "" {
|
||||
dsn = ":memory:"
|
||||
}
|
||||
db, err := sql.Open("duckdb", dsn)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("duckdb_open: open %q: %w", dsn, err)
|
||||
}
|
||||
if err := db.Ping(); err != nil {
|
||||
db.Close()
|
||||
return nil, fmt.Errorf("duckdb_open: ping %q: %w", dsn, err)
|
||||
}
|
||||
return db, nil
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
---
|
||||
name: duckdb_open
|
||||
kind: function
|
||||
lang: go
|
||||
domain: infra
|
||||
version: "1.0.0"
|
||||
purity: impure
|
||||
signature: "func DuckDBOpen(path string) (*sql.DB, error)"
|
||||
description: "Abre (o crea) una base de datos DuckDB. Path vacio o ':memory:' abre una base en memoria."
|
||||
tags: [database, duckdb, connection, sql, analytics]
|
||||
uses_functions: []
|
||||
uses_types: [db_config_go_infra]
|
||||
returns: []
|
||||
returns_optional: false
|
||||
error_type: "error_go_core"
|
||||
imports: ["database/sql", "github.com/marcboeker/go-duckdb"]
|
||||
tested: false
|
||||
tests: []
|
||||
test_file_path: ""
|
||||
file_path: "functions/infra/duckdb_open.go"
|
||||
---
|
||||
|
||||
## Ejemplo
|
||||
|
||||
```go
|
||||
// In-memory para analisis temporal
|
||||
db, err := DuckDBOpen("")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer DBClose(db)
|
||||
|
||||
rows, err := DBQuery(db, "SELECT * FROM read_parquet('/data/sales.parquet')")
|
||||
```
|
||||
|
||||
## Notas
|
||||
|
||||
Usa el driver `github.com/marcboeker/go-duckdb` (CGO). DuckDB es una base de datos OLAP embebida, ideal para analisis de datos. Path vacio equivale a `:memory:`. Hace ping al abrir para detectar errores temprano.
|
||||
@@ -0,0 +1,32 @@
|
||||
package infra
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
|
||||
_ "github.com/jackc/pgx/v5/stdlib"
|
||||
)
|
||||
|
||||
// PostgresOpen connects to a PostgreSQL server and returns a *sql.DB.
|
||||
// sslmode defaults to "disable" when empty.
|
||||
// Constructs a DSN of the form:
|
||||
//
|
||||
// host=<host> port=<port> user=<user> password=<password> dbname=<dbname> sslmode=<sslmode>
|
||||
func PostgresOpen(host string, port int, user, password, dbname string, sslmode string) (*sql.DB, error) {
|
||||
if sslmode == "" {
|
||||
sslmode = "disable"
|
||||
}
|
||||
dsn := fmt.Sprintf(
|
||||
"host=%s port=%d user=%s password=%s dbname=%s sslmode=%s",
|
||||
host, port, user, password, dbname, sslmode,
|
||||
)
|
||||
db, err := sql.Open("pgx", dsn)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("postgres_open: open: %w", err)
|
||||
}
|
||||
if err := db.Ping(); err != nil {
|
||||
db.Close()
|
||||
return nil, fmt.Errorf("postgres_open: ping %s:%d/%s: %w", host, port, dbname, err)
|
||||
}
|
||||
return db, nil
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
---
|
||||
name: postgres_open
|
||||
kind: function
|
||||
lang: go
|
||||
domain: infra
|
||||
version: "1.0.0"
|
||||
purity: impure
|
||||
signature: "func PostgresOpen(host string, port int, user, password, dbname string, sslmode string) (*sql.DB, error)"
|
||||
description: "Conecta a PostgreSQL construyendo el DSN desde parametros individuales. sslmode por defecto 'disable' si vacio."
|
||||
tags: [database, postgres, postgresql, connection, sql]
|
||||
uses_functions: []
|
||||
uses_types: [db_config_go_infra]
|
||||
returns: []
|
||||
returns_optional: false
|
||||
error_type: "error_go_core"
|
||||
imports: ["database/sql", "github.com/jackc/pgx/v5/stdlib"]
|
||||
tested: false
|
||||
tests: []
|
||||
test_file_path: ""
|
||||
file_path: "functions/infra/postgres_open.go"
|
||||
---
|
||||
|
||||
## Ejemplo
|
||||
|
||||
```go
|
||||
db, err := PostgresOpen("localhost", 5432, "user", "secret", "mydb", "disable")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer DBClose(db)
|
||||
|
||||
rows, err := DBQuery(db, "SELECT id, name FROM users WHERE active = $1", true)
|
||||
```
|
||||
|
||||
## Notas
|
||||
|
||||
Usa el driver `github.com/jackc/pgx/v5/stdlib` registrado como "pgx". Construye DSN con los parametros separados para mayor legibilidad. Para produccion usar `sslmode=require` o `sslmode=verify-full`. Hace ping al abrir para verificar conectividad.
|
||||
@@ -0,0 +1,27 @@
|
||||
package infra
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
|
||||
_ "github.com/mattn/go-sqlite3"
|
||||
)
|
||||
|
||||
// SQLiteOpen opens (or creates) a SQLite database file with WAL mode and
|
||||
// foreign key support enabled. Returns a ready-to-use *sql.DB or an error.
|
||||
// Pass ":memory:" for an in-memory database.
|
||||
func SQLiteOpen(path string) (*sql.DB, error) {
|
||||
if path == "" {
|
||||
return nil, fmt.Errorf("sqlite_open: path must not be empty (use ':memory:' for in-memory)")
|
||||
}
|
||||
dsn := fmt.Sprintf("file:%s?_journal_mode=WAL&_foreign_keys=on", path)
|
||||
db, err := sql.Open("sqlite3", dsn)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("sqlite_open: open %q: %w", path, err)
|
||||
}
|
||||
if err := db.Ping(); err != nil {
|
||||
db.Close()
|
||||
return nil, fmt.Errorf("sqlite_open: ping %q: %w", path, err)
|
||||
}
|
||||
return db, nil
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
---
|
||||
name: sqlite_open
|
||||
kind: function
|
||||
lang: go
|
||||
domain: infra
|
||||
version: "1.0.0"
|
||||
purity: impure
|
||||
signature: "func SQLiteOpen(path string) (*sql.DB, error)"
|
||||
description: "Abre (o crea) una base de datos SQLite con WAL mode y foreign keys habilitados. Hace ping para verificar la conexion."
|
||||
tags: [database, sqlite, connection, sql]
|
||||
uses_functions: []
|
||||
uses_types: [db_config_go_infra]
|
||||
returns: []
|
||||
returns_optional: false
|
||||
error_type: "error_go_core"
|
||||
imports: ["database/sql", "github.com/mattn/go-sqlite3"]
|
||||
tested: false
|
||||
tests: []
|
||||
test_file_path: ""
|
||||
file_path: "functions/infra/sqlite_open.go"
|
||||
---
|
||||
|
||||
## Ejemplo
|
||||
|
||||
```go
|
||||
db, err := SQLiteOpen("/data/myapp.db")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer DBClose(db)
|
||||
|
||||
rows, err := DBQuery(db, "SELECT * FROM users WHERE active = ?", 1)
|
||||
```
|
||||
|
||||
## Notas
|
||||
|
||||
Usa el driver `github.com/mattn/go-sqlite3` (CGO). El DSN incluye `_journal_mode=WAL` para mejor concurrencia y `_foreign_keys=on`. Acepta `:memory:` para base de datos en memoria. Hace ping al abrir para detectar errores temprano.
|
||||
@@ -0,0 +1,21 @@
|
||||
---
|
||||
name: db_config
|
||||
lang: go
|
||||
domain: infra
|
||||
version: "1.0.0"
|
||||
algebraic: product
|
||||
definition: |
|
||||
type DBConfig struct {
|
||||
Driver string // "sqlite", "duckdb", "postgres", "clickhouse"
|
||||
DSN string // Data source name / connection string
|
||||
Opts map[string]string // Driver-specific options (optional)
|
||||
}
|
||||
description: "Parametros de conexion para cualquier base de datos soportada. Agnóstico al driver."
|
||||
tags: [database, config, connection, sqlite, duckdb, postgres, clickhouse]
|
||||
uses_types: []
|
||||
file_path: "functions/infra/db_config.go"
|
||||
---
|
||||
|
||||
## Notas
|
||||
|
||||
Tipo producto — todos los campos siempre presentes. Driver toma uno de los valores: "sqlite", "duckdb", "postgres", "clickhouse". DSN es el connection string nativo del driver. Opts permite pasar opciones adicionales especificas del driver sin necesidad de un tipo por driver.
|
||||
Reference in New Issue
Block a user