Spaces:
Running
Running
Download src/selfheal_sql/make_splits.py from HideIron/selfheal-sql: direct link, hf CLI and curl.
- Browser
- Download file 4.47 kB
-
https://huggingface.co/spaces/HideIron/selfheal-sql/resolve/main/src/selfheal_sql/make_splits.py
- Command line
-
hf download hf://spaces/HideIron/selfheal-sql/src/selfheal_sql/make_splits.py
-
curl -L -o make_splits.py https://huggingface.co/spaces/HideIron/selfheal-sql/resolve/main/src/selfheal_sql/make_splits.py
4.47 kB
| """Build the three DATABASE-DISJOINT splits from Spider. | |
| pool (failure pool) - questions the untrained student attempts; its failures | |
| feed the heal loop. Source: Spider train, DB group A. | |
| gate (held-out gate) - DeploymentGate A/B set; never used to make training data. | |
| Source: Spider train, DB group B (different DBs from pool). | |
| test (final test) - headline before/after numbers. | |
| Source: Spider dev (Spider's own dev DBs never appear in train). | |
| Disjoint DBs mean a "heal" cannot pass by memorising a schema it already saw. | |
| Rows whose gold query errors, times out, or returns ZERO rows are dropped: an empty | |
| gold result would let any empty-result query count as correct. | |
| Usage: python -I make_splits.py <spider_data_dir> <out_dir> [pool gate test seed] | |
| """ | |
| from __future__ import annotations | |
| import json | |
| import os | |
| import random | |
| import sys | |
| from execcheck import check, schema_text # noqa: E402 (run from this folder) | |
| def load(path): | |
| with open(path, encoding="utf-8") as f: | |
| return json.load(f) | |
| def usable(db_path: str, gold_sql: str) -> bool: | |
| # gold vs gold: must run, and must return at least one row | |
| import sqlite3 | |
| from execcheck import _connect, _run # noqa: PLC0415 | |
| try: | |
| conn = _connect(db_path) | |
| try: | |
| rows = _run(conn, gold_sql, 10.0) | |
| finally: | |
| conn.close() | |
| except (sqlite3.Error, Exception): # noqa: BLE001 | |
| return False | |
| return len(rows) > 0 | |
| def build_rows(items, db_dir, want, rng, tag): | |
| rng.shuffle(items) | |
| rows, schemas = [], {} | |
| for it in items: | |
| if len(rows) >= want: | |
| break | |
| db_id = it["db_id"] | |
| db_path = os.path.join(db_dir, db_id, f"{db_id}.sqlite") | |
| if not os.path.exists(db_path) or not usable(db_path, it["query"]): | |
| continue | |
| if db_id not in schemas: | |
| schemas[db_id] = schema_text(db_path) | |
| rows.append( | |
| { | |
| "qid": f"{tag}_{len(rows):04d}", | |
| "split": tag, | |
| "db_id": db_id, | |
| "question": it["question"], | |
| "gold_sql": it["query"], | |
| "db_path": db_path, | |
| "schema": schemas[db_id], | |
| } | |
| ) | |
| return rows | |
| def main(): | |
| spider = sys.argv[1] | |
| out = sys.argv[2] | |
| n_pool, n_gate, n_test, seed = ( | |
| int(sys.argv[3]) if len(sys.argv) > 3 else 200, | |
| int(sys.argv[4]) if len(sys.argv) > 4 else 100, | |
| int(sys.argv[5]) if len(sys.argv) > 5 else 100, | |
| int(sys.argv[6]) if len(sys.argv) > 6 else 0, | |
| ) | |
| rng = random.Random(seed) | |
| db_dir = os.path.join(spider, "database") | |
| train = load(os.path.join(spider, "train_spider.json")) | |
| dev = load(os.path.join(spider, "dev.json")) | |
| # Split TRAIN databases into two disjoint groups (pool vs gate). | |
| train_dbs = sorted({x["db_id"] for x in train}) | |
| rng.shuffle(train_dbs) | |
| cut = int(len(train_dbs) * 0.65) | |
| group_a, group_b = set(train_dbs[:cut]), set(train_dbs[cut:]) | |
| pool_items = [x for x in train if x["db_id"] in group_a] | |
| gate_items = [x for x in train if x["db_id"] in group_b] | |
| pool = build_rows(pool_items, db_dir, n_pool, rng, "pool") | |
| gate = build_rows(gate_items, db_dir, n_gate, rng, "gate") | |
| test = build_rows(list(dev), db_dir, n_test, rng, "test") | |
| # Hard guarantees, asserted not assumed. | |
| dbs = {k: {r["db_id"] for r in v} for k, v in (("pool", pool), ("gate", gate), ("test", test))} | |
| assert not (dbs["pool"] & dbs["gate"]), "pool/gate share a database" | |
| assert not (dbs["pool"] & dbs["test"]), "pool/test share a database" | |
| assert not (dbs["gate"] & dbs["test"]), "gate/test share a database" | |
| os.makedirs(out, exist_ok=True) | |
| for name, rows in (("pool", pool), ("gate", gate), ("test", test)): | |
| with open(os.path.join(out, f"{name}.jsonl"), "w", encoding="utf-8") as f: | |
| for r in rows: | |
| f.write(json.dumps(r, ensure_ascii=False) + "\n") | |
| print(f"{name}: {len(rows)} questions over {len(dbs[name])} databases") | |
| # Sanity: gold-vs-gold must be 100% correct under the checker. | |
| bad = [r["qid"] for rows in (pool, gate, test) for r in rows | |
| if not check(r["db_path"], r["gold_sql"], r["gold_sql"]).correct] | |
| print("gold-vs-gold failures:", len(bad), bad[:5]) | |
| print("db-disjoint: OK") | |
| if __name__ == "__main__": | |
| main() | |