--- name: duckdb_table_schema kind: function lang: py domain: infra version: "1.0.0" purity: impure signature: "def duckdb_table_schema(db_path: str, table: str) -> dict" description: "Devuelve el schema (columnas y tipos) de una tabla 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. Ejecuta DESCRIBE con el identificador de tabla validado contra ^[A-Za-z_][A-Za-z0-9_]*$ y citado (DESCRIBE no admite parametros posicionales). Devuelve un dict sin lanzar (estilo del grupo duckdb): {status:'ok', table, columns:[{name,type}]} en exito y {status:'error', error} en fallo. type es el tipo DuckDB tal cual (BIGINT, DOUBLE, VARCHAR...). Es la introspeccion de columnas del grupo duckdb, util para mapear tipos a otro motor (p.ej. PostgreSQL). Depende del paquete duckdb (1.5.2 en python/.venv)." tags: [duckdb, sql, introspection, schema, readonly] uses_functions: [] uses_types: [] returns: [] returns_optional: false error_type: "error_py_core" imports: [re, duckdb] params: - name: db_path desc: "ruta al archivo DuckDB. Debe existir: el modo read_only NO crea la base. Un path inexistente devuelve {status:'error'}." - name: table desc: "nombre de la tabla a inspeccionar. Se valida contra ^[A-Za-z_][A-Za-z0-9_]*$ antes de interpolarlo en el DESCRIBE (que no admite parametro posicional para el identificador). Un identificador invalido devuelve {status:'error'} sin tocar la base." output: "dict. En exito: {status:'ok', table:str, columns:[{name:str, type:str},...]} donde type es el tipo DuckDB tal cual lo reporta el motor. En error (sin lanzar): {status:'error', error:str}." tested: true tests: - "test_schema_devuelve_columnas_y_tipos" - "test_identificador_invalido_devuelve_status_error" - "test_tabla_inexistente_devuelve_status_error" - "test_db_inexistente_devuelve_status_error" test_file_path: "python/functions/infra/duckdb_table_schema_test.py" file_path: "python/functions/infra/duckdb_table_schema.py" --- ## Ejemplo ```python import sys sys.path.insert(0, "python/functions") import duckdb from infra.duckdb_table_schema import duckdb_table_schema db = "/tmp/almacen.duckdb" con = duckdb.connect(db) con.execute("CREATE TABLE ventas (id BIGINT, region VARCHAR, total DOUBLE, ok BOOLEAN)") con.close() res = duckdb_table_schema(db, "ventas") print(res["status"]) # ok print(res["table"]) # ventas print(res["columns"]) # [{'name': 'id', 'type': 'BIGINT'}, {'name': 'region', 'type': 'VARCHAR'}, # {'name': 'total', 'type': 'DOUBLE'}, {'name': 'ok', 'type': 'BOOLEAN'}] ``` ## Cuando usarla Cuando necesitas el schema de una tabla DuckDB sin abrir la base en escritura: mapear tipos DuckDB a otro motor (es el paso (a) de `duckdb_to_postgres_py_pipelines`), validar que una tabla tiene las columnas esperadas tras una ingesta, o mostrar el schema en una UI. Usa `duckdb_list_tables_py_infra` antes para descubrir que tablas hay. El dict de salida es directamente serializable a JSON. ## Gotchas - Lectura real de un archivo en disco (impura). El modo `read_only=True` exige que el archivo **ya exista**: no crea la base. Si `db_path` no existe, devuelve `{status:'error', ...}`. - El identificador `table` se valida contra `^[A-Za-z_][A-Za-z0-9_]*$` porque DESCRIBE NO admite parametro posicional para el nombre de tabla y hay que interpolarlo. Un nombre con espacios, comillas, puntos o intento de inyeccion devuelve `{status:'error', error:'invalid table identifier'}` sin tocar la base. - El `type` es el tipo DuckDB literal (`BIGINT`, `DOUBLE`, `VARCHAR`, `DECIMAL(10,2)`, `STRUCT(...)`, ...). Si lo vas a traducir a otro motor, contempla los tipos parametrizados y compuestos: pueden requerir mapeo con perdida (a TEXT). - DuckDB es single-writer: una base bloqueada en escritura por otro proceso con version distinta puede fallar al abrir en read-only; el error se devuelve, no se lanza.