fix(eda): bugs de bajo riesgo del benchmark (H1,H5,H12,H13,H14) + tests faltantes
- H1: render_eda_markdown ya no aplica doble x100 a outlier_pct (336% -> real) - H5: profile_database filtra base_tables_only (excluye VIEWs; sakila 21->16) - H12: suggest_reexpression salta columnas no-continuas - H13: to_returns/profile_table elige retornos (financiera) vs diferencias (fisica) - H14: test de regresion ATTACH sqlite via information_schema - +8 tests de las funciones eda nuevas (acf_pacf, adf_kpss, ...). 77 tests verdes - L/M (H2,H3,H4,H6,H7,H8,H9,H10,H11) quedan en issues 0174-0177 para revision Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -5,8 +5,8 @@ lang: py
|
||||
domain: infra
|
||||
version: "1.0.0"
|
||||
purity: impure
|
||||
signature: "def duckdb_list_tables(db_path: str) -> dict"
|
||||
description: "Lista las tablas de 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. Consulta information_schema.tables del esquema main y devuelve los nombres ordenados alfabeticamente. Devuelve un dict sin lanzar (estilo del grupo duckdb): {status:'ok', tables} en exito y {status:'error', error} en fallo. Es la introspeccion 'que tablas hay' del grupo duckdb; complementa a duckdb_query_readonly_py_infra (lectura de filas) y a duckdb_table_schema_py_infra (schema de una tabla). Depende del paquete duckdb (1.5.2 en python/.venv)."
|
||||
signature: "def duckdb_list_tables(db_path: str, base_tables_only: bool = False) -> dict"
|
||||
description: "Lista las tablas de 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. Consulta information_schema.tables del esquema main y devuelve los nombres ordenados alfabeticamente. Con base_tables_only=True filtra table_type='BASE TABLE', excluyendo las VIEWs (util para perfilar/relacionar solo tablas reales). Devuelve un dict sin lanzar (estilo del grupo duckdb): {status:'ok', tables} en exito y {status:'error', error} en fallo. Es la introspeccion 'que tablas hay' del grupo duckdb; complementa a duckdb_query_readonly_py_infra (lectura de filas) y a duckdb_table_schema_py_infra (schema de una tabla). Depende del paquete duckdb (1.5.2 en python/.venv)."
|
||||
tags: [duckdb, sql, introspection, readonly, tables]
|
||||
uses_functions: []
|
||||
uses_types: []
|
||||
@@ -17,12 +17,16 @@ imports: [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: base_tables_only
|
||||
desc: "si True (default False) filtra table_type='BASE TABLE', excluyendo las VIEWs del esquema main. Util para perfilar/relacionar solo tablas reales (perfilar una VIEW infla el conteo y multiplica relaciones FK falsas)."
|
||||
output: "dict. En exito: {status:'ok', tables:[str,...]} con los nombres de tabla del esquema main ordenados alfabeticamente. En error (sin lanzar): {status:'error', error:str}."
|
||||
tested: true
|
||||
tests:
|
||||
- "test_lista_tablas_ordenadas"
|
||||
- "test_base_vacia_devuelve_lista_vacia"
|
||||
- "test_db_inexistente_devuelve_status_error"
|
||||
- "test_base_tables_only_excluye_views"
|
||||
- "test_attach_sqlite_materializado_lista_por_information_schema"
|
||||
test_file_path: "python/functions/infra/duckdb_list_tables_test.py"
|
||||
file_path: "python/functions/infra/duckdb_list_tables.py"
|
||||
---
|
||||
@@ -64,7 +68,8 @@ selector de tablas en una UI. Es el primer paso natural antes de
|
||||
- DuckDB es single-writer: si otro proceso tiene la base abierta en escritura con
|
||||
una version distinta del motor, la apertura read-only puede fallar con error de
|
||||
lock. El error se devuelve como `{status:'error', ...}`, no se lanza.
|
||||
- Solo lista tablas del esquema `main` (el por defecto). Vistas y tablas de otros
|
||||
esquemas no aparecen.
|
||||
- Solo lista objetos del esquema `main` (el por defecto); tablas de otros esquemas
|
||||
no aparecen. Por defecto incluye **vistas** (table_type VIEW) además de las tablas
|
||||
base; pasa `base_tables_only=True` para quedarte solo con las `BASE TABLE`.
|
||||
- Una base recien creada sin tablas devuelve `{status:'ok', tables:[]}` (no es un
|
||||
error): lista vacia.
|
||||
|
||||
@@ -13,12 +13,19 @@ introspeccion de alto nivel "que tablas hay" del grupo duckdb.
|
||||
"""
|
||||
|
||||
|
||||
def duckdb_list_tables(db_path: str) -> dict:
|
||||
def duckdb_list_tables(db_path: str, base_tables_only: bool = False) -> dict:
|
||||
"""Lista las tablas de una base DuckDB en modo solo lectura.
|
||||
|
||||
Args:
|
||||
db_path: ruta al archivo DuckDB. Debe existir: el modo read_only NO crea
|
||||
la base. Un path inexistente devuelve {status:'error', ...}.
|
||||
base_tables_only: si True (default False) filtra por
|
||||
`table_type = 'BASE TABLE'`, excluyendo las VIEWs (y demas objetos no
|
||||
tabla-base) del esquema `main`. Util para perfilar/relacionar solo las
|
||||
tablas reales: perfilar una VIEW infla el numero de tablas y multiplica
|
||||
las relaciones FK falsas. El default mantiene el comportamiento previo
|
||||
(lista todo lo que aparece en information_schema.tables del esquema
|
||||
main) para no romper consumidores existentes.
|
||||
|
||||
Returns:
|
||||
dict. En exito: {status:'ok', tables:[str,...]} con los nombres de tabla
|
||||
@@ -28,10 +35,14 @@ def duckdb_list_tables(db_path: str) -> dict:
|
||||
conn = None
|
||||
try:
|
||||
conn = __import__("duckdb").connect(db_path, read_only=True)
|
||||
rows = conn.execute(
|
||||
sql = (
|
||||
"SELECT table_name FROM information_schema.tables "
|
||||
"WHERE table_schema = 'main' ORDER BY table_name"
|
||||
).fetchall()
|
||||
"WHERE table_schema = 'main'"
|
||||
)
|
||||
if base_tables_only:
|
||||
sql += " AND table_type = 'BASE TABLE'"
|
||||
sql += " ORDER BY table_name"
|
||||
rows = conn.execute(sql).fetchall()
|
||||
tables = [row[0] for row in rows]
|
||||
return {"status": "ok", "tables": tables}
|
||||
except Exception as e: # noqa: BLE001
|
||||
|
||||
@@ -38,3 +38,59 @@ def test_db_inexistente_devuelve_status_error(tmp_path):
|
||||
res = duckdb_list_tables(str(tmp_path / "noexiste.duckdb"))
|
||||
assert res["status"] == "error"
|
||||
assert "error" in res
|
||||
|
||||
|
||||
def test_base_tables_only_excluye_views(tmp_path):
|
||||
# Una BASE TABLE + una VIEW: por defecto se listan ambas; con
|
||||
# base_tables_only=True la VIEW se excluye.
|
||||
db = tmp_path / "withviews.duckdb"
|
||||
con = duckdb.connect(str(db))
|
||||
con.execute("CREATE TABLE ventas (id INTEGER, total DOUBLE)")
|
||||
con.execute("CREATE VIEW ventas_resumen AS SELECT id FROM ventas")
|
||||
con.close()
|
||||
|
||||
# Default: incluye la view.
|
||||
res_all = duckdb_list_tables(str(db))
|
||||
assert res_all["status"] == "ok"
|
||||
assert res_all["tables"] == ["ventas", "ventas_resumen"]
|
||||
|
||||
# base_tables_only: solo la tabla base.
|
||||
res_base = duckdb_list_tables(str(db), base_tables_only=True)
|
||||
assert res_base["status"] == "ok"
|
||||
assert res_base["tables"] == ["ventas"]
|
||||
|
||||
|
||||
def test_attach_sqlite_materializado_lista_por_information_schema(tmp_path):
|
||||
# Regresión H14: tras ATTACH de una base SQLite en DuckDB se materializan sus
|
||||
# tablas y se listan vía information_schema (NO sqlite_master, que no existe en
|
||||
# DuckDB). duckdb_list_tables debe verlas como tablas del esquema main.
|
||||
import sqlite3
|
||||
|
||||
sqlite_path = str(tmp_path / "src.sqlite")
|
||||
sconn = sqlite3.connect(sqlite_path)
|
||||
sconn.execute("CREATE TABLE clientes (id INTEGER PRIMARY KEY, nombre TEXT)")
|
||||
sconn.execute("INSERT INTO clientes VALUES (1,'Ana'),(2,'Luis')")
|
||||
sconn.execute("CREATE VIEW clientes_v AS SELECT id FROM clientes")
|
||||
sconn.commit()
|
||||
sconn.close()
|
||||
|
||||
ddb_path = str(tmp_path / "materialized.duckdb")
|
||||
con = duckdb.connect(ddb_path)
|
||||
con.execute("INSTALL sqlite")
|
||||
con.execute("LOAD sqlite")
|
||||
con.execute(f"ATTACH '{sqlite_path}' AS src (TYPE sqlite)")
|
||||
# Listar tablas base del catálogo attachado por information_schema (no
|
||||
# sqlite_master) y materializarlas como tablas nativas DuckDB.
|
||||
rows = con.execute(
|
||||
"SELECT table_name FROM information_schema.tables "
|
||||
"WHERE table_catalog='src' AND table_type='BASE TABLE' "
|
||||
"AND table_name NOT LIKE 'sqlite_%'"
|
||||
).fetchall()
|
||||
for (name,) in rows:
|
||||
con.execute(f'CREATE TABLE "{name}" AS SELECT * FROM src."{name}"')
|
||||
con.execute("DETACH src")
|
||||
con.close()
|
||||
|
||||
res = duckdb_list_tables(ddb_path)
|
||||
assert res["status"] == "ok"
|
||||
assert "clientes" in res["tables"]
|
||||
|
||||
Reference in New Issue
Block a user