Files
fn_registry/python/functions/infra/duckdb_execute.md
T
egutierrez 1c4a4b9259 feat(duckdb,dav): primitivas de escritura DuckDB + libretas CardDAV + vCard multi-valor
Cinco funciones nuevas para soportar DuckDB como fuente de verdad del project osint:

Grupo duckdb (escritura, complementan a duckdb_query_readonly):
- duckdb_execute_py_infra (impure): ejecuta INSERT/UPDATE/DELETE/DDL en read-write, commit, {status,rowcount}. 6 tests.
- duckdb_upsert_py_infra (impure): UPSERT ON CONFLICT actualizando solo update_cols → ownership selectivo (un re-upsert no pisa columnas excluidas). 7 tests.

Grupo dav (libretas de contactos + vCard multi-valor):
- dav_make_addressbook_py_infra (impure): crea una libreta CardDAV nueva via extended MKCOL (RFC 5689). Idempotente. 12 tests.
- dav_list_addressbooks_py_infra (impure): lista las libretas del contacts-home (PROPFIND Depth:1). 7 tests.
- build_vcard_py_core (pure): serializa un contacto a vCard 3.0 multi-valor (N TEL/EMAIL/ADR + X-OSINT-*). 5 tests.

Paginas de capacidad duckdb.md y dav.md actualizadas.

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

4.8 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_execute function py infra 1.0.0 impure def duckdb_execute(db_path: str, sql: str, params: list = None) -> dict Ejecuta UNA sentencia de escritura (INSERT/UPDATE/DELETE/DDL) contra una base DuckDB abierta en conexion read-write (duckdb.connect(db_path)), hace commit y cierra siempre en try/finally. En modo escritura DuckDB crea el archivo si no existe. Es el primitivo de escritura del grupo duckdb; complementa a duckdb_query_readonly_py_infra (solo lectura). Usa parametros posicionales con el marcador '?'. Devuelve un dict sin lanzar (estilo del grupo): {status:'ok', rowcount} en exito y {status:'error', error} en fallo. rowcount es el numero de filas afectadas; DuckDB no expone un rowcount fiable (siempre -1) pero tras INSERT/UPDATE/DELETE el fetchall() del cursor devuelve [(n,)] de donde se extrae; para DDL u operaciones sin filas queda en -1 sin fallar. Depende del paquete duckdb (1.5.2 en python/.venv).
duckdb
sql
execute
write
ddl
dml
false error_py_core
duckdb
name desc
db_path ruta al archivo DuckDB. En modo escritura DuckDB crea el archivo si no existe. Un directorio padre inexistente o un lock de otro proceso devuelve {status:'error'}.
name desc
sql sentencia SQL de escritura (INSERT/UPDATE/DELETE/DDL). 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.
dict. En exito: {status:'ok', rowcount:int} donde rowcount es el numero de filas afectadas (o -1 cuando la sentencia no reporta filas, p.ej. DDL). En error (sin lanzar): {status:'error', error:str}. true
test_insert_devuelve_status_ok_y_persiste
test_update_afecta_filas_y_persiste
test_delete_afecta_filas_y_persiste
test_ddl_create_table_status_ok
test_crea_la_base_si_no_existe
test_sql_invalido_devuelve_status_error
python/functions/infra/duckdb_execute_test.py python/functions/infra/duckdb_execute.py

Ejemplo

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

db = "/tmp/eventos.duckdb"

# DDL: crear la tabla (la base se crea sola si no existia).
print(duckdb_execute(db, "CREATE TABLE eventos (id INTEGER, tipo VARCHAR)"))
# {'status': 'ok', 'rowcount': -1}   (DDL no reporta filas)

# DML: insertar con parametros posicionales.
res = duckdb_execute(
    db,
    "INSERT INTO eventos VALUES (?, ?), (?, ?)",
    params=[1, "login", 2, "logout"],
)
print(res)
# {'status': 'ok', 'rowcount': 2}

# UPDATE.
print(duckdb_execute(db, "UPDATE eventos SET tipo = ? WHERE id = ?", params=["signin", 1]))
# {'status': 'ok', 'rowcount': 1}

Cuando usarla

Cuando un service single-writer necesita escribir DDL/DML en su DuckDB: crear o migrar tablas, insertar registros nuevos, actualizar estado o borrar filas en un archivo DuckDB que ese proceso posee. Es la mitad de escritura del grupo duckdb: usa duckdb_query_readonly_py_infra para leer (sin riesgo de modificar la base) y duckdb_execute_py_infra para escribir con commit. El dict de salida con rowcount es directamente serializable a JSON para pasarlo al siguiente paso de una composicion.

Gotchas

  • Escritura real de un archivo en disco (impura). Abre en modo read-write y hace commit; cualquier fallo se devuelve como {status:'error', ...}, nunca se lanza.
  • DuckDB es single-writer: solo un proceso puede tener la base abierta en escritura a la vez. Si otro proceso ya la tiene abierta en write, connect falla con un error de lock (Could not set lock on file ...) que se devuelve como {status:'error', ...}. Diseña el acceso para que un unico proceso sea el escritor; los lectores deben usar duckdb_query_readonly (read_only=True).
  • rowcount no es fiable en todos los casos. DuckDB no expone un cursor.rowcount util (siempre devuelve -1); esta funcion lee el conteo del fetchall() que DuckDB emite tras INSERT/UPDATE/DELETE ([(n,)]). Para DDL (CREATE/DROP/ ALTER) y operaciones que no reportan filas, rowcount queda en -1 a proposito: NO trates -1 como error.
  • Ejecuta UNA sentencia por llamada (con.execute(sql, params)). No es para scripts multi-statement separados por ;; para eso encadena varias llamadas o usa una funcion/pipeline dedicada.
  • Los parametros van en params con el marcador ?, nunca interpolados en el string del SQL (previene inyeccion).
  • A diferencia del modo read-only, este modo crea el archivo si no existe. Un db_path con un directorio padre inexistente si falla y se reporta como error.