Files
fn_registry/python/functions/infra/duckdb_query_readonly.md
T
egutierrez d89da1292d chore: auto-commit (9 archivos)
- docs/capabilities/INDEX.md
- docs/capabilities/obsidian.md
- python/functions/core/render_markdown_table.md
- python/functions/core/render_markdown_table.py
- python/functions/core/render_markdown_table_test.py
- python/functions/core/upsert_sentinel_block.md
- python/functions/core/upsert_sentinel_block.py
- python/functions/core/upsert_sentinel_block_test.py
- python/functions/infra/duckdb_query_readonly.md

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-13 21:56:56 +02:00

4.6 KiB

name, kind, lang, domain, version, purity, signature, description, tags, uses_functions, uses_types, returns, returns_optional, error_type, imports, params, output, tested, tests, test_file_path, file_path
name kind lang domain version purity signature description tags uses_functions uses_types returns returns_optional error_type imports params output tested tests test_file_path file_path
duckdb_query_readonly function py infra 1.0.0 impure def duckdb_query_readonly(db_path: str, sql: str, params: list = None, max_rows: int = 10000) -> dict Ejecuta una query SELECT contra una base DuckDB abierta en modo solo lectura (duckdb.connect(db_path, read_only=True)), de modo que nunca crea ni modifica la base. La conexion se cierra siempre en try/finally. Usa parametros posicionales con el marcador '?'. Devuelve un dict sin lanzar (estilo del grupo dav): {status:'ok', columns, rows, row_count, truncated} en exito y {status:'error', error} en fallo. Las filas son list[dict]. Trunca a max_rows para proteger memoria. Convierte valores no serializables: date/datetime/time a isoformat(), Decimal a float, bytes a base64, UUID a str. Depende del paquete duckdb (1.5.2 en python/.venv).
duckdb
sql
query
readonly
false error_py_core
base64
datetime
decimal
uuid
duckdb
name desc
db_path ruta al archivo DuckDB. Debe existir: el modo read_only NO crea la base. Un path inexistente devuelve {status:'error'}.
name desc
sql sentencia SQL a ejecutar (pensada para SELECT). Usa el marcador '?' para parametros posicionales.
name desc
params lista de parametros posicionales para el SQL en orden. None (default) significa sin parametros. Pasar valores aqui en vez de interpolarlos en el SQL evita inyeccion.
name desc
max_rows numero maximo de filas a materializar en memoria. Default 10000. Si la query produce mas, el resultado se trunca y truncated queda en True.
dict. En exito: {status:'ok', columns:[str,...], rows:[{col:val,...},...], row_count:int, truncated:bool}. En error (sin lanzar): {status:'error', error:str}. Los valores de las filas estan normalizados a tipos JSON-serializables. true
test_query_ok_devuelve_filas_como_dicts
test_query_con_params_posicionales
test_sql_invalido_devuelve_status_error
test_db_inexistente_devuelve_status_error
test_truncado_a_max_rows
test_valores_no_serializables_se_convierten
python/functions/infra/duckdb_query_readonly_test.py python/functions/infra/duckdb_query_readonly.py

Ejemplo

import sys
sys.path.insert(0, "python/functions")
import duckdb
from infra.duckdb_query_readonly import duckdb_query_readonly

# Preparamos una base de ejemplo (esto seria un proceso separado en la realidad).
db = "/tmp/ventas.duckdb"
con = duckdb.connect(db)
con.execute("CREATE TABLE ventas (id INTEGER, region VARCHAR, total DECIMAL(10,2))")
con.execute("INSERT INTO ventas VALUES (1, 'norte', 120.50), (2, 'sur', 80.00), (3, 'norte', 45.25)")
con.close()

# Lectura solo-lectura con parametro posicional.
res = duckdb_query_readonly(
    db,
    "SELECT region, SUM(total) AS total FROM ventas WHERE region = ? GROUP BY region",
    params=["norte"],
)
print(res["status"])      # ok
print(res["columns"])     # ['region', 'total']
print(res["rows"])        # [{'region': 'norte', 'total': 165.75}]
print(res["truncated"])   # False

Cuando usarla

Cuando necesitas leer datos de un archivo DuckDB sin riesgo de modificarlo: inspeccionar una base materializada, validar el resultado de un pipeline, alimentar un dashboard o un report, o consultar tablas/Parquet exportados por otra funcion del registry. El modo read_only garantiza que la consulta nunca crea ni altera la base, y el dict de salida es directamente serializable a JSON para pasarlo al siguiente paso de una composicion.

Gotchas

  • Lectura real de un archivo en disco (impura). El modo read_only=True exige que el archivo ya exista: a diferencia del modo escritura, no crea la base. Si db_path no existe, devuelve {status:'error', error:...}.
  • Conflicto de lock: si otro proceso tiene la misma base abierta en escritura con una version de DuckDB distinta, la apertura puede fallar (DuckDB no permite abrir un archivo bloqueado por otra version del motor). El error se devuelve como {status:'error', ...}, no se lanza.
  • max_rows protege la memoria: una query que devuelve millones de filas se trunca a max_rows y marca truncated=True. Si necesitas todas las filas, pagina con LIMIT/OFFSET en el SQL o sube max_rows conscientemente.
  • Los parametros van en params con el marcador ?, nunca interpolados en el string del SQL (previene inyeccion).
  • Valores no JSON-serializables se normalizan en la salida: date/datetime/time a isoformat(), Decimal a float (puede haber perdida de precision frente al decimal exacto), bytes a base64 y UUID a str.