File size: 4,376 Bytes
f636e22
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
"""Enlarge the held-out gate split (100 -> N) so small accuracy differences are measurable.

The original gate has 100 questions (std. error ~ +/-5pp), too noisy to tell a 2pp
regression from chance. This keeps the original 100 rows (same qids, so old results stay
comparable) and adds more questions drawn from the SAME databases the gate already uses
(Spider-train DB group B), so:
  * no database appears in pool or test (asserted),
  * every database is already in the HF dataset repo (no DB upload needed),
  * gold queries run and return >=1 row (same rule as make_splits.py).

Usage (from repo root):
  python src/selfheal_sql/make_gate_large.py [--total 400] [--seed 1]
Writes data/splits/gate_large.jsonl (absolute db_path, like the other local splits) and
data/hf_dataset/splits/gate_large.jsonl (relative db_path, ready to upload).
"""

from __future__ import annotations

import argparse
import json
import os
import random
import sys

HERE = os.path.dirname(os.path.abspath(__file__))
sys.path.insert(0, HERE)
from execcheck import check, schema_text  # noqa: E402
from make_splits import usable  # noqa: E402

ROOT = os.path.abspath(os.path.join(HERE, "..", ".."))
SPIDER = os.path.join(ROOT, "data", "spider", "spider_data")
SPLITS = os.path.join(ROOT, "data", "splits")
STAGE = os.path.join(ROOT, "data", "hf_dataset", "splits")


def read(path):
    with open(path, encoding="utf-8") as f:
        return [json.loads(line) for line in f]


def main() -> None:
    ap = argparse.ArgumentParser()
    ap.add_argument("--total", type=int, default=400)
    ap.add_argument("--seed", type=int, default=1)
    args = ap.parse_args()

    pool, gate, test = (read(os.path.join(SPLITS, f"{n}.jsonl")) for n in ("pool", "gate", "test"))
    gate_dbs = {r["db_id"] for r in gate}
    seen = {(r["db_id"], r["question"]) for r in gate}

    with open(os.path.join(SPIDER, "train_spider.json"), encoding="utf-8") as f:
        train = json.load(f)
    # Candidates: train questions on the gate's databases, not already in the gate.
    cands = [x for x in train if x["db_id"] in gate_dbs and (x["db_id"], x["question"]) not in seen]
    random.Random(args.seed).shuffle(cands)

    rows = [dict(r, split="gate_large") for r in gate]  # keep original 100, same qids
    schemas = {r["db_id"]: r["schema"] for r in gate}
    n_keep = len(rows)
    for it in cands:
        if len(rows) >= args.total:
            break
        db_id = it["db_id"]
        db_path = os.path.join(SPIDER, "database", db_id, f"{db_id}.sqlite")
        if not os.path.exists(db_path) or not usable(db_path, it["query"]):
            continue
        schemas.setdefault(db_id, schema_text(db_path))
        rows.append({
            "qid": f"gate_{len(rows):04d}", "split": "gate_large", "db_id": db_id,
            "question": it["question"], "gold_sql": it["query"],
            "db_path": db_path, "schema": schemas[db_id],
        })

    # Hard guarantees, asserted not assumed.
    dbs = {r["db_id"] for r in rows}
    assert dbs == gate_dbs, "gate_large introduced a new database"
    assert not dbs & {r["db_id"] for r in pool}, "gate_large shares a DB with pool"
    assert not dbs & {r["db_id"] for r in test}, "gate_large shares a DB with test"
    assert len({r["qid"] for r in rows}) == len(rows), "duplicate qid"
    assert len({(r["db_id"], r["question"]) for r in rows}) == len(rows), "duplicate question"
    bad = [r["qid"] for r in rows if not check(r["db_path"], r["gold_sql"], r["gold_sql"]).correct]
    print(f"gate_large: {len(rows)} questions ({n_keep} original + {len(rows) - n_keep} new) "
          f"over {len(dbs)} databases; gold-vs-gold failures: {len(bad)}")
    assert not bad, bad[:5]

    os.makedirs(SPLITS, exist_ok=True)
    with open(os.path.join(SPLITS, "gate_large.jsonl"), "w", encoding="utf-8") as f:
        for r in rows:
            f.write(json.dumps(r, ensure_ascii=False) + "\n")

    os.makedirs(STAGE, exist_ok=True)
    with open(os.path.join(STAGE, "gate_large.jsonl"), "w", encoding="utf-8") as f:
        for r in rows:
            r2 = dict(r, db_path=f"databases/{r['db_id']}/{r['db_id']}.sqlite")
            f.write(json.dumps(r2, ensure_ascii=False) + "\n")
    print("wrote data/splits/gate_large.jsonl and data/hf_dataset/splits/gate_large.jsonl")
    print("db-disjoint from pool/test: OK")


if __name__ == "__main__":
    main()