English | 한국어
Fold a wide physical schema into a few wide logical models an LLM can read in one shot, then expand the LLM's logical SQL back into executable physical SQL.
53 physical tables ──fold──▶ 7 logical models (~3k tokens)
◀─expand── executable SQL
Text-to-SQL often fails on real databases because of long DDL and complex joins. tablefold groups related tables into wide models such as orders and products, so the LLM does not have to reason about joins.
uv sync
uv sync --extra postgres # to read the schema directly from PostgreSQL# Fold the physical schema into logical models
tablefold fold --ddl fixtures/retail_50.sql
# Print schema context for an LLM prompt
tablefold context --ddl fixtures/retail_50.sql --field-budget 120
# Expand logical SQL into physical SQL
tablefold expand "SELECT customer_tier_label, SUM(grand_total) FROM orders GROUP BY 1" \
--ddl fixtures/retail_50.sqlPython API:
from tablefold.introspect.ddl import DDLIntrospector
from tablefold.pipeline import fold
from tablefold.expand import expand
schema = DDLIntrospector.from_path("schema.sql").introspect()
result = fold(schema)
print(expand("SELECT SUM(grand_total) FROM orders", result.layer, result.graph).sql)- Introspect: Reads tables, columns, and keys from a DDL file or PostgreSQL.
- FK recovery: Infers missing foreign keys from naming rules such as
customer_id→customers.id. - Anchor selection: Scores fact tables and uses greedy set cover to reach maximum coverage with the fewest models. With
--llm, an LLM refines model names and domain decisions. - Synthesis: N:1 relations are flattened in. 1:N child tables are pre-aggregated with
GROUP BYto prevent row fan-out. - Expand: Keeps only the joins needed for the fields the query actually uses and builds physical CTE SQL.
Without the LLM option, the whole process is deterministic. See docs/pipeline_architecture.md (Korean) for the full design.
| Option | Default | Description |
|---|---|---|
--coverage |
0.90 |
Target table coverage |
--min-gain |
2 |
Minimum number of new tables one model must cover |
--max-cost |
10 |
Allowed fields per new table |
--llm |
off | Use an LLM to refine anchor selection and model names |
uv run pytest tests/
uv run ruff check src tests