Spaces:
Running
Running
File size: 9,249 Bytes
58afff2 | 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 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 | """
WikiSQL Data Processing Script v5
Build schema-aware inputs for WikiSQL to fine-tune T5/CodeT5/BART.
Input: WikiSQL/{train,dev,test}.{jsonl,tables.jsonl}
Output: processed/wikisql/{train,dev,test}.json
Usage:
python src/process_wikisql.py
python src/process_wikisql.py --no_column_types
"""
import re
import json
import argparse
import sys
from pathlib import Path
try:
sys.stdout.reconfigure(encoding='utf-8')
except Exception:
pass
try:
import sqlglot
HAS_SQLGLOT = True
except ImportError:
HAS_SQLGLOT = False
print("WARNING: sqlglot not installed. Run: pip install sqlglot")
AGG_OPS = ['', 'MAX', 'MIN', 'COUNT', 'SUM', 'AVG']
COND_OPS = ['=', '>', '<', '!=', 'LIKE']
TABLE_NAME = "data_table"
WIKISQL_TYPE_MAP = {'text': 'text', 'real': 'number'}
SQL_KEYWORDS = {
'select', 'from', 'where', 'and', 'or', 'not', 'in', 'is', 'null', 'like',
'order', 'by', 'group', 'having', 'limit', 'join', 'left', 'right', 'inner',
'outer', 'on', 'as', 'distinct', 'count', 'sum', 'avg', 'max', 'min',
'between', 'case', 'when', 'then', 'else', 'end', 'union', 'all', 'insert',
'update', 'delete', 'drop', 'create', 'table', 'index', 'view', 'exists',
'with', 'into', 'values', 'set', 'alter', 'add', 'column', 'primary', 'key',
'foreign', 'references', 'check', 'default', 'unique', 'cast', 'true', 'false',
}
SPECIAL_CHARS = set(" /-.(),%#'\"&+*?:;!@$\\<>=[]{}|~`^")
# ββ SQL utilities ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
def normalize_sql(sql):
"""Normalize SQL for Exact Match β lowercase everything (WikiSQL DB is already lowercase)."""
if not sql:
return ""
if HAS_SQLGLOT:
try:
return sqlglot.parse_one(sql, dialect='sqlite').sql(
dialect='sqlite', pretty=False
).lower().strip()
except Exception:
pass
sql = sql.strip().lower()
sql = re.sub(r'\s+', ' ', sql)
sql = re.sub(r'\s*,\s*', ' , ', sql)
sql = re.sub(r'\s*(=|!=|<>|>=|<=|>|<)\s*', r' \1 ', sql)
return sql.strip()
def check_sql_validity(sql):
if not sql or not sql.strip():
return False
if HAS_SQLGLOT:
try:
sqlglot.parse_one(sql, dialect='sqlite')
return True
except Exception:
return False
return sql.strip().upper().startswith('SELECT')
# ββ Quote helpers ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
def quote_column(col_name):
"""Double-quote column name if it contains special characters or is an SQL keyword."""
if not col_name or not col_name.strip():
return '"unknown_column"'
needs_quote = (
any(c in SPECIAL_CHARS for c in col_name) or
col_name[0].isdigit() or
col_name.lower() in SQL_KEYWORDS
)
return f'"{col_name}"' if needs_quote else col_name
def quote_value(val):
"""Format WHERE value β lowercase string, numeric as-is."""
if isinstance(val, str):
return f"'{val.lower().replace(chr(39), chr(39)*2)}'"
return str(val)
# ββ Core conversion ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
def convert_sql_to_string(sql_json, header):
"""Reconstruct SQL string from WikiSQL structured annotation {sel, agg, conds}."""
sel_col = quote_column(header[sql_json['sel']])
agg = AGG_OPS[sql_json['agg']]
select = f"SELECT {agg}({sel_col})" if agg else f"SELECT {sel_col}"
conds = [
f"{quote_column(header[c[0]])} {COND_OPS[c[1]]} {quote_value(c[2])}"
for c in sql_json['conds']
]
where = " WHERE " + " AND ".join(conds) if conds else ""
return (select + f" FROM {TABLE_NAME}" + where).lower()
def build_schema_string(header, types=None, use_column_types=True):
col_parts = []
for i, col_name in enumerate(header):
col = quote_column(col_name)
if use_column_types and types and i < len(types):
col += f":{WIKISQL_TYPE_MAP.get(types[i], types[i])}"
col_parts.append(col)
return f"{TABLE_NAME} ( {' , '.join(col_parts)} )"
def build_model_input(question, header, types=None, use_column_types=True):
schema = build_schema_string(header, types=types, use_column_types=use_column_types)
return f"question: {question.strip()} | schema: {schema}"
# ββ Data loading βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
def load_tables(tables_path):
tables = {}
with open(tables_path, 'r', encoding='utf-8') as f:
for line in f:
if line.strip():
t = json.loads(line)
tables[t['id']] = {'header': t['header'], 'types': t.get('types', [])}
print(f" Loaded {len(tables):,} tables from {Path(tables_path).name}")
return tables
# ββ Process split ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
def process_split(data_path, tables_path, split_name, use_column_types=True):
print(f"\n[{split_name.upper()}] Processing...")
tables = load_tables(tables_path)
dataset, errors, not_found, invalid_sql = [], 0, 0, 0
with open(data_path, 'r', encoding='utf-8') as f:
for idx, line in enumerate(f):
if not line.strip():
continue
try:
item = json.loads(line)
table_id = item['table_id']
if table_id not in tables or not tables[table_id]['header']:
not_found += 1
continue
header = tables[table_id]['header']
types = tables[table_id].get('types', [])
sql_str = convert_sql_to_string(item['sql'], header)
valid = check_sql_validity(sql_str)
if not valid:
invalid_sql += 1
dataset.append({
'input': build_model_input(item['question'], header,
types=types,
use_column_types=use_column_types),
'output': sql_str,
'normalized_output': normalize_sql(sql_str),
'is_valid_sql': valid,
'question': item['question'].strip(),
'table_id': table_id,
'phase': item.get('phase', 1),
})
except Exception as e:
errors += 1
if errors <= 5:
print(f" WARNING line {idx}: {e}")
print(f" Processed: {len(dataset):,} | Not found: {not_found} | "
f"Errors: {errors} | Invalid SQL: {invalid_sql}")
return dataset
# ββ Main βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
def main():
parser = argparse.ArgumentParser(description='Process WikiSQL v5')
parser.add_argument('--data_dir', default=r'C:\Users\ADMIN\Documents\Text to SQL\WikiSQL')
parser.add_argument('--output_dir', default=r'C:\Users\ADMIN\Documents\Text to SQL\processed\wikisql')
parser.add_argument('--no_column_types', action='store_true', default=False,
help='Ablation: remove :type annotation from schema')
args = parser.parse_args()
data_dir = Path(args.data_dir)
output_dir = Path(args.output_dir)
use_column_types = not args.no_column_types
output_dir.mkdir(parents=True, exist_ok=True)
splits = {
'train': ('train.jsonl', 'train.tables.jsonl'),
'dev': ('dev.jsonl', 'dev.tables.jsonl'),
'test': ('test.jsonl', 'test.tables.jsonl'),
}
for split_name, (data_file, tables_file) in splits.items():
data_path = data_dir / data_file
tables_path = data_dir / tables_file
if not data_path.exists() or not tables_path.exists():
print(f"WARNING: Missing files for {split_name}, skipping")
continue
dataset = process_split(str(data_path), str(tables_path), split_name,
use_column_types=use_column_types)
out = output_dir / f"{split_name}.json"
with open(out, 'w', encoding='utf-8') as f:
json.dump(dataset, f, ensure_ascii=False, indent=2)
size = out.stat().st_size / 1e6
print(f" Saved -> {out} ({len(dataset):,} samples, {size:.1f} MB)")
if __name__ == '__main__':
main()
|