feat(infra): auto-commit con 56 cambios
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -15,12 +15,22 @@ Postgres es la **capa que sirve datos a las herramientas de BI** del stack (`Exc
|
||||
| `pg_create_table_from_rows_py_infra` | `pg_create_table_from_rows(dsn, table, rows, primary_key=None) -> dict` | `CREATE TABLE IF NOT EXISTS` infiriendo columnas y tipos desde los valores (bool→BOOLEAN, int→BIGINT, float→DOUBLE PRECISION, datetime→TIMESTAMP, date→DATE, resto→TEXT). Idempotente. Devuelve `{status, created, table, columns}`. |
|
||||
| `pg_list_tables_py_infra` | `pg_list_tables(dsn, schema='public') -> dict` | Introspección read-only: tablas base con sus columnas vía `information_schema`. Devuelve `{status, schema, tables:[{name, columns:[{name,type,nullable}]}]}`. |
|
||||
| `pg_apply_sql_py_infra` | `pg_apply_sql(dsn, sql_path) -> int` | Ejecuta un archivo `.sql` completo (multi-statement, una transacción). Para migraciones idempotentes (`IF NOT EXISTS`). |
|
||||
| `resolve_pg_dsn_py_infra` | `resolve_pg_dsn(project) -> dict` | Resuelve el DSN de Postgres de un proyecto conocido (`captacion`/`captacion_clientes` vía `CAPTACION_DSN`, `seo`/`seo_analytics` vía `SEO_DSN`) en este orden: (1) variable de entorno, (2) línea `<ENV_VAR>=` del `.env` del proyecto, (3) construido desde `pass` en runtime. Devuelve `{status, project, dsn, source}` (`source` = `env`\|`dotenv`\|`pass`) sin lanzar. Mapa de proyectos explícito en el código — añadir un proyecto = editar `_PROJECTS`. Nunca hardcodea el password. |
|
||||
| `query_project_pg_py_pipelines` | `query_project_pg(project, sql, max_rows=10000) -> dict` | **Pipeline one-shot**: compone `resolve_pg_dsn` + `pg_query`. Lee el DSN del proyecto y ejecuta el SELECT en un solo paso, sin que el caller toque el DSN. Devuelve lo de `pg_query` (`{status, columns, rows, row_count, truncated}`) o propaga el error de resolución. Reemplaza el patrón inline de resolver el DSN a mano antes de consultar. |
|
||||
|
||||
Relacionadas (otros grupos): `duckdb_to_postgres_py_pipelines` (sincroniza una tabla DuckDB a Postgres) e `init_metabase_go_infra` (despliega el stack Metabase + Postgres en Docker).
|
||||
|
||||
## Ejemplo canónico
|
||||
|
||||
Crear una tabla inferida, hacer upsert idempotente y consultar (DSN desde `pass`):
|
||||
Atajo de un paso — consultar un proyecto conocido sin tocar el DSN (resuelto desde `.env`/`pass`):
|
||||
|
||||
```bash
|
||||
cd /home/enmanuel/fn_registry
|
||||
./fn run query_project_pg captacion "SELECT COUNT(*) AS n FROM product_opportunities"
|
||||
# {"status":"ok","columns":["n"],"rows":[{"n":19}],"row_count":1,"truncated":false}
|
||||
```
|
||||
|
||||
Camino completo — crear una tabla inferida, hacer upsert idempotente y consultar (DSN desde `pass`):
|
||||
|
||||
```bash
|
||||
cd /home/enmanuel/fn_registry
|
||||
@@ -42,7 +52,7 @@ PYEOF
|
||||
|
||||
## Gotchas del grupo
|
||||
|
||||
- **El DSN lleva credenciales — nunca hardcodear.** Resuélvelo desde `pass` (ej. `pass captacion/postgres`: L1 = password, resto `user/host/port/datadb`). No imprimas el DSN en logs.
|
||||
- **El DSN lleva credenciales — nunca hardcodear.** Resuélvelo desde `pass` (ej. `pass captacion/postgres`: L1 = password, resto `user/host/port/datadb`), o mejor con `resolve_pg_dsn(project)` que centraliza la convención por proyecto. No imprimas el DSN en logs. Para proyectos no mapeados en `resolve_pg_dsn`, pasa el DSN a `pg_query` directamente.
|
||||
- **`pg_query`/`pg_list_tables` son read-only por convención** (`SET TRANSACTION READ ONLY` + rollback), protegen la base pero NO son sandbox; los identificadores (tabla/schema) NO se parametrizan — los valores sí (`%s`). Las funciones validan identificadores con `^[A-Za-z_][A-Za-z0-9_]*$`.
|
||||
- **`pg_upsert` cuenta insert vs update con el pseudo-columna `xmax`** (`RETURNING (xmax = 0)`). Fiable en el caso normal (single-writer, sin triggers raros). Con `update_cols=[]` (DO NOTHING) las filas en conflicto no se devuelven, así que solo se cuentan las nuevas. BEFORE-triggers / REPLICA IDENTITY pueden desviar el conteo.
|
||||
- **`pg_create_table_from_rows` no reconcilia schema:** si la tabla ya existe, `columns` reporta los tipos inferidos de las filas, no los reales. Inferencia best-effort sin NUMERIC/escala — para dinero define el schema a mano con `pg_apply_sql`.
|
||||
|
||||
Reference in New Issue
Block a user