| """Walk a SQLAlchemy engine to extract tables, columns, PK/FK, and per-column |
| column-value statistics (top-K samples, null count, distinct count). |
| |
| Dialect-agnostic via SQLAlchemy reflection + programmatic SELECTs (no raw SQL |
| strings, so identifier quoting is handled by the dialect). This module runs |
| *offline* during indexing and assumes the engine is read-only. |
| """ |
|
|
| from __future__ import annotations |
|
|
| from dataclasses import dataclass |
| from typing import Any |
|
|
| from sqlalchemy import MetaData, Table, func, inspect, select |
| from sqlalchemy.engine import Engine |
| from sqlalchemy.exc import SQLAlchemyError |
|
|
|
|
| @dataclass(frozen=True, slots=True) |
| class ColumnInfo: |
| name: str |
| type: str |
| nullable: bool |
| is_primary_key: bool |
| sample_values: tuple[Any, ...] |
| null_count: int |
| distinct_count: int |
|
|
|
|
| @dataclass(frozen=True, slots=True) |
| class ForeignKeyInfo: |
| columns: tuple[str, ...] |
| referred_table: str |
| referred_columns: tuple[str, ...] |
|
|
|
|
| @dataclass(frozen=True, slots=True) |
| class TableInfo: |
| name: str |
| columns: tuple[ColumnInfo, ...] |
| primary_key_columns: tuple[str, ...] |
| foreign_keys: tuple[ForeignKeyInfo, ...] |
| row_count: int |
|
|
| @property |
| def column_names(self) -> tuple[str, ...]: |
| return tuple(c.name for c in self.columns) |
|
|
|
|
| def introspect( |
| engine: Engine, |
| *, |
| sample_size: int = 5, |
| sample_value_max_chars: int = 80, |
| ) -> list[TableInfo]: |
| """Return one `TableInfo` per user table in the engine's default schema. |
| |
| Per-column stats (`sample_values`, `null_count`, `distinct_count`) are |
| sourced via SELECT-only queries against the live engine; for empty tables |
| (`row_count == 0`) the stats are skipped and reported as empty/zero. |
| |
| `sample_value_max_chars` truncates very long string samples so that schema |
| chunks stay embed-friendly. |
| """ |
| insp = inspect(engine) |
| table_names = sorted(insp.get_table_names()) |
| metadata = MetaData() |
|
|
| tables: list[TableInfo] = [] |
| with engine.connect() as conn: |
| for tname in table_names: |
| sa_table = Table(tname, metadata, autoload_with=engine) |
| row_count = conn.execute(select(func.count()).select_from(sa_table)).scalar_one() |
| row_count_int = int(row_count or 0) |
|
|
| pk_constraint = insp.get_pk_constraint(tname) |
| pk_cols: tuple[str, ...] = tuple(pk_constraint.get("constrained_columns") or ()) |
|
|
| fk_records = insp.get_foreign_keys(tname) |
| fks = tuple( |
| ForeignKeyInfo( |
| columns=tuple(fk.get("constrained_columns") or ()), |
| referred_table=fk["referred_table"], |
| referred_columns=tuple(fk.get("referred_columns") or ()), |
| ) |
| for fk in fk_records |
| if fk.get("referred_table") |
| ) |
|
|
| cols: list[ColumnInfo] = [] |
| for col_meta in insp.get_columns(tname): |
| col_name = col_meta["name"] |
| col_type = str(col_meta.get("type")) |
| nullable = bool(col_meta.get("nullable", True)) |
| is_pk = col_name in pk_cols |
|
|
| sa_col = sa_table.c[col_name] |
| if row_count_int == 0: |
| samples: tuple[Any, ...] = () |
| null_count = 0 |
| distinct_count = 0 |
| else: |
| null_count = int( |
| conn.execute( |
| select(func.count()).select_from(sa_table).where(sa_col.is_(None)) |
| ).scalar_one() |
| or 0 |
| ) |
| distinct_count = int( |
| conn.execute( |
| select(func.count(func.distinct(sa_col))).select_from(sa_table) |
| ).scalar_one() |
| or 0 |
| ) |
| samples = _top_k_samples( |
| conn, |
| sa_table, |
| sa_col, |
| k=sample_size, |
| max_chars=sample_value_max_chars, |
| ) |
|
|
| cols.append( |
| ColumnInfo( |
| name=col_name, |
| type=col_type, |
| nullable=nullable, |
| is_primary_key=is_pk, |
| sample_values=samples, |
| null_count=null_count, |
| distinct_count=distinct_count, |
| ) |
| ) |
|
|
| tables.append( |
| TableInfo( |
| name=tname, |
| columns=tuple(cols), |
| primary_key_columns=pk_cols, |
| foreign_keys=fks, |
| row_count=row_count_int, |
| ) |
| ) |
|
|
| return tables |
|
|
|
|
| def _top_k_samples( |
| conn: Any, |
| sa_table: Table, |
| sa_col: Any, |
| *, |
| k: int, |
| max_chars: int, |
| ) -> tuple[Any, ...]: |
| """Return top-k most frequent non-null values for `sa_col`, truncating |
| long strings. Falls back to `()` if the dialect rejects the query (e.g. |
| BLOB/JSON columns that can't be GROUP BY'd on some engines). |
| """ |
| try: |
| rows = conn.execute( |
| select(sa_col, func.count()) |
| .where(sa_col.is_not(None)) |
| .group_by(sa_col) |
| .order_by(func.count().desc(), sa_col) |
| .limit(k) |
| ).all() |
| except SQLAlchemyError: |
| return () |
| return tuple(_truncate(row[0], max_chars) for row in rows) |
|
|
|
|
| def _truncate(value: Any, max_chars: int) -> Any: |
| if isinstance(value, str) and len(value) > max_chars: |
| return value[: max_chars - 1] + "…" |
| return value |
|
|
|
|
| def fetch_extended_samples( |
| engine: Engine, |
| table_names: list[str], |
| *, |
| primary_size: int, |
| extended_size: int, |
| sample_value_max_chars: int = 80, |
| ) -> dict[str, dict[str, tuple[Any, ...]]]: |
| """Return the *tail* slice of top-k samples per column, per table. |
| |
| For each table in `table_names`, fetches the top-`extended_size` most |
| frequent values per column (same ranking used in `introspect`), then |
| drops the first `primary_size` to return only the delta — values that |
| were NOT already shown in the primary schema cards. |
| |
| Empty result for a column means: either the table has fewer than |
| `primary_size` distinct values, or extended_size <= primary_size. |
| |
| Used by the per-difficulty sample mixture path: the schema_block keeps |
| its compact `primary_size` cards; this delta is appended in a clearly |
| labelled "additional sample values" section so codestral can use it |
| only when needed (challenging-tier filter-value discovery). |
| """ |
| if extended_size <= primary_size: |
| return {} |
| insp = inspect(engine) |
| available = set(insp.get_table_names()) |
| metadata = MetaData() |
| out: dict[str, dict[str, tuple[Any, ...]]] = {} |
| with engine.connect() as conn: |
| for tname in table_names: |
| if tname not in available: |
| continue |
| sa_table = Table(tname, metadata, autoload_with=engine) |
| row_count = conn.execute(select(func.count()).select_from(sa_table)).scalar_one() |
| if not int(row_count or 0): |
| continue |
| per_col: dict[str, tuple[Any, ...]] = {} |
| for col_meta in insp.get_columns(tname): |
| col_name = col_meta["name"] |
| sa_col = sa_table.c[col_name] |
| full = _top_k_samples( |
| conn, |
| sa_table, |
| sa_col, |
| k=extended_size, |
| max_chars=sample_value_max_chars, |
| ) |
| tail = full[primary_size:] |
| if tail: |
| per_col[col_name] = tail |
| if per_col: |
| out[tname] = per_col |
| return out |
|
|