68f4ddabce
Añade el capítulo `relaciones` al motor AutomaticEDA: analiza las relaciones de clave de la tabla/base y se coloca tras `correlacion`, antes de `modelos`, en CHAPTER_ORDER. Capas que renderiza (solo las que aplican; None si no hay nada que decir): - Claves declaradas: PK/FK/UNIQUE reales del esquema DuckDB, vía la nueva función `detect_declared_keys_duckdb` (lee `duckdb_constraints()`). - Candidatos a clave primaria: los `key_candidates` del TableProfile. - FK candidatas inter-tabla: reusa `infer_fk_containment_duckdb` (containment + señal de nombre) y `build_join_graph` (roles de nodos + diagrama Mermaid pegable). Solo si la fuente DuckDB tiene varias tablas. - FK candidatas intra-tabla: heurística nombre + cardinalidad, vía la nueva función pura `suggest_intratable_fk_candidates`, marcada como sugerencia. Engancha al glosario clicable los términos PK, FK, containment/inclusión y cardinalidad (contrato §11.1) y usa Group (keep-together) para el grafo. Funciones nuevas del registry (grupo `eda`): - detect_declared_keys_duckdb (impure, datascience) + test. - suggest_intratable_fk_candidates (pure, datascience) + test. Tests: relaciones_test.py (golden intra + inter, edges, no-cut render) + los tests de ambas funciones. Suite automatic_eda + render_automatic_eda verde (89 passed). Golden end-to-end con el pipeline render_automatic_eda verificado sobre titanic (intra) y una BD customers/orders (inter). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
7.1 KiB
7.1 KiB
name, kind, lang, domain, version, purity, signature, description, tags, params, output, uses_functions, uses_types, returns, returns_optional, error_type, imports, tested, tests, test_file_path, file_path
| name | kind | lang | domain | version | purity | signature | description | tags | params | output | uses_functions | uses_types | returns | returns_optional | error_type | imports | tested | tests | test_file_path | file_path | |||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| detect_declared_keys_duckdb | function | py | datascience | 1.0.0 | impure | def detect_declared_keys_duckdb(db_path: str, table: str = None) -> dict | Detecta las claves DECLARADAS (constraints reales) de un schema DuckDB leyendo la table function duckdb_constraints(): extrae PRIMARY KEY, FOREIGN KEY y UNIQUE (ignora NOT NULL y CHECK) y las devuelve normalizadas con sus columnas, y para las FK con su tabla y columnas referenciadas. Con table=None procesa todas las tablas; con table='X' filtra a PK/UNIQUE de X y a FK cuyo origen es X (case-sensitive). A diferencia de infer_fk_containment_duckdb (que INFIERE FKs candidatas por containment de valores cuando el schema no las declara), esta funcion devuelve las relaciones de clave REALES del schema. Estilo dict-no-throw: nunca lanza. Parte del grupo eda (relaciones de clave). |
|
|
dict dict-no-throw. En exito {status:'ok', primary_keys:[{table:str, columns:[str,...]}, ...], foreign_keys:[{table:str, columns:[str,...], referenced_table:str, referenced_columns:[str,...]}, ...], unique:[{table:str, columns:[str,...]}, ...], tables:[str,...]} donde tables es la lista ordenada de tablas (origen) que poseen al menos un constraint PK/FK/UNIQUE emitido. Solo se emiten constraints de clave: NOT NULL y CHECK se ignoran. En error {status:'error', error:str}. |
|
false | error_go_core | true |
|
python/functions/datascience/detect_declared_keys_duckdb_test.py | python/functions/datascience/detect_declared_keys_duckdb.py |
Ejemplo
import sys, os, duckdb
sys.path.insert(0, os.path.join("python", "functions"))
from datascience import detect_declared_keys_duckdb
# Base de ejemplo en /tmp: orders.customer_id -> customers.id (FK declarada)
path = "/tmp/declared_keys_demo.duckdb"
if os.path.exists(path):
os.remove(path)
con = duckdb.connect(path)
con.execute("CREATE TABLE customers(id INTEGER PRIMARY KEY, name TEXT)")
con.execute(
"CREATE TABLE orders("
" id INTEGER PRIMARY KEY,"
" customer_id INTEGER REFERENCES customers(id),"
" amt DOUBLE)"
)
con.close()
res = detect_declared_keys_duckdb(path)
if res["status"] == "ok":
for pk in res["primary_keys"]:
print(f"PK {pk['table']}({', '.join(pk['columns'])})")
for fk in res["foreign_keys"]:
print(f"FK {fk['table']}({', '.join(fk['columns'])}) -> "
f"{fk['referenced_table']}({', '.join(fk['referenced_columns'])})")
# PK customers(id)
# PK orders(id)
# FK orders(customer_id) -> customers(id)
else:
print("error:", res["error"])
# Filtrar a una tabla concreta (PK/UNIQUE de orders + FK con origen orders):
solo_orders = detect_declared_keys_duckdb(path, table="orders")
print(solo_orders["tables"]) # ['orders']
Cuando usarla
- Cuando exploras un esquema DuckDB y quieres mostrar las relaciones de clave REALES (PK/FK/UNIQUE) que el schema ha declarado, sin inferir nada.
- Como paso del capitulo RELACIONES del grupo
eda: primero mira las claves declaradas con esta funcion; si el schema no declara FKs, complementa coninfer_fk_containment_duckdb(inferencia por containment). - Antes de documentar o migrar un esquema, para listar el contrato de integridad referencial que el motor ya conoce.
- Para validar que las constraints que esperas (esa FK que creaste con
REFERENCES) realmente estan declaradas en la base materializada.
Gotchas
- Impura: lee de disco via la primitiva read-only
duckdb_query_readonly(no crea ni modifica la base). Eldb_pathdebe existir; un path inexistente devuelve{status:'error'}(read_only NO crea la base). - Requiere
duckdb_constraints(): usa la table functionduckdb_constraints(), disponible en DuckDB modernos (verificado en 1.5.2). En versiones antiguas sin esa funcion, la query falla y se devuelve{status:'error'}. - Solo claves DECLARADAS: devuelve lo que el schema declaro con
PRIMARY KEY/FOREIGN KEY (... REFERENCES ...)/UNIQUE. Una tabla materializada conCREATE TABLE AS SELECTNO lleva constraints — para esos casos no habra claves que mostrar y hay que INFERIRLAS (infer_fk_containment_duckdb). - NOT NULL y CHECK se ignoran:
duckdb_constraints()tambien emite filasNOT NULL(DuckDB genera una por cada columna PK) yCHECK; esta funcion las descarta y solo conserva PK/FK/UNIQUE. - Nombres case-sensitive: el filtro
table='Orders'no casa con una tablaorders. Se comparan los nombres tal cual los devuelve DuckDB. - FK atribuida al origen: una FOREIGN KEY se atribuye a su tabla ORIGEN (el
tablede la entrada), no a la referenciada. El filtrotable='X'trae las FK cuyo origen es X, no las que apuntan a X. tables= tablas dueñas de constraints emitidos: la listatablescontiene solo las tablas que poseen al menos un PK/FK/UNIQUE en el resultado (su campotable), ordenadas. No incluye tablas referenciadas que no tengan constraint propio en la salida.- Columnas como listas:
constraint_column_namesyreferenced_column_namesson columnas LIST de DuckDB; en 1.5.2 llegan como listas Python. La funcion las normaliza a listas de strings con una red de seguridad por si llegaran como string.
Notas
duckdb_constraints() devuelve una fila por constraint con los campos
table_name, constraint_type, constraint_column_names, referenced_table,
referenced_column_names. Mapeo a la salida:
PRIMARY KEY -> primary_keys[]: {table, columns}
UNIQUE -> unique[]: {table, columns}
FOREIGN KEY -> foreign_keys[]: {table, columns, referenced_table, referenced_columns}
NOT NULL -> ignorado
CHECK -> ignorado
Para una FK, referenced_table y referenced_column_names vienen poblados; para
PK/UNIQUE, referenced_table es NULL y referenced_column_names una lista vacia.
Complementa a infer_fk_containment_duckdb: esta funcion devuelve las relaciones
de clave REALES del schema (declaradas); la otra INFIERE FKs candidatas por
containment de valores cuando el schema no las declaro. En el capitulo RELACIONES
de AutomaticEDA se usan en orden: primero las declaradas, luego la inferencia como
respaldo.