Skip to content

Commit 1f9122e

Browse files
committed
test: add reviewed PostgreSQL syntax witnesses
1 parent bcc707b commit 1f9122e

5 files changed

Lines changed: 390 additions & 0 deletions

File tree

scripts/pg_compat/baseline.py

Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
import copy
44
import json
5+
import subprocess
6+
from pathlib import Path
57

68
if __package__:
79
from .common import atomic_write_text
@@ -185,6 +187,195 @@ def add_case(row):
185187
return [selected[record_id] for record_id in sorted(selected)]
186188

187189

190+
def _read_json_array(path, label):
191+
path = Path(path)
192+
with path.open(encoding="utf-8") as input_file:
193+
value = json.load(input_file)
194+
if not isinstance(value, list):
195+
raise ValueError(f"{label} must contain a JSON array")
196+
return value
197+
198+
199+
def parse_witnesses_with_runner(
200+
sql_path,
201+
runner_path,
202+
*,
203+
branch,
204+
commit,
205+
timeout_seconds=30,
206+
):
207+
result = subprocess.run(
208+
[
209+
str(runner_path),
210+
"--input",
211+
str(sql_path),
212+
"--branch",
213+
branch,
214+
"--commit",
215+
commit,
216+
],
217+
capture_output=True,
218+
text=True,
219+
timeout=timeout_seconds,
220+
check=False,
221+
)
222+
if result.returncode != 0:
223+
raise RuntimeError(
224+
"witness runner failed: "
225+
f"{result.stderr.strip() or result.stdout.strip()}"
226+
)
227+
228+
rows = []
229+
for line_number, line in enumerate(result.stdout.splitlines(), start=1):
230+
if not line.strip():
231+
continue
232+
try:
233+
rows.append(json.loads(line))
234+
except json.JSONDecodeError as error:
235+
raise ValueError(
236+
f"witness runner stdout line {line_number}: invalid JSON: "
237+
f"{error.msg}"
238+
) from error
239+
return rows
240+
241+
242+
def _validate_metadata_row(row, index, target_major):
243+
if not isinstance(row, dict):
244+
raise ValueError(f"witness metadata row {index}: expected a JSON object")
245+
246+
witness_id = row.get("id")
247+
if not isinstance(witness_id, str) or not witness_id:
248+
raise ValueError(
249+
f"witness metadata row {index}: field 'id' must be a "
250+
"non-empty string"
251+
)
252+
253+
first_major = row.get("first_postgresql_major")
254+
if (
255+
isinstance(first_major, bool)
256+
or not isinstance(first_major, int)
257+
or first_major < 1
258+
or first_major > target_major
259+
):
260+
raise ValueError(
261+
f"witness metadata row {index}: field 'first_postgresql_major' "
262+
f"must be an integer from 1 to {target_major}"
263+
)
264+
265+
expected_node = row.get("expected_oracle_node")
266+
if not isinstance(expected_node, str) or not expected_node:
267+
raise ValueError(
268+
f"witness metadata row {index}: field 'expected_oracle_node' "
269+
"must be a non-empty string"
270+
)
271+
272+
feature_ids = row.get("structural_feature_ids")
273+
if not isinstance(feature_ids, list):
274+
raise ValueError(
275+
f"witness metadata row {index}: field 'structural_feature_ids' "
276+
"must be a list"
277+
)
278+
for feature_index, feature_id in enumerate(feature_ids):
279+
if not isinstance(feature_id, str) or not feature_id:
280+
raise ValueError(
281+
f"witness metadata row {index}: structural_feature_ids"
282+
f"[{feature_index}] must be a non-empty string"
283+
)
284+
285+
note = row.get("note")
286+
if note is not None and not isinstance(note, str):
287+
raise ValueError(
288+
f"witness metadata row {index}: field 'note' must be a string"
289+
)
290+
291+
292+
def _validate_oracle_witness_row(row, index):
293+
if not isinstance(row, dict):
294+
raise ValueError(f"witness oracle row {index}: expected a JSON object")
295+
for field in ("oracle_node", "result", "sql"):
296+
value = row.get(field)
297+
if not isinstance(value, str) or not value:
298+
raise ValueError(
299+
f"witness oracle row {index}: field {field!r} must be a "
300+
"non-empty string"
301+
)
302+
if row["result"] == "ORACLE_REJECTED":
303+
raise ValueError(
304+
f"witness oracle row {index}: statement must be oracle accepted"
305+
)
306+
307+
308+
def validate_witnesses(oracle_rows, metadata_rows, *, target_major):
309+
if isinstance(target_major, bool) or not isinstance(target_major, int):
310+
raise ValueError("target_major must be an integer")
311+
if target_major < 1:
312+
raise ValueError("target_major must be positive")
313+
314+
if len(oracle_rows) != len(metadata_rows):
315+
raise ValueError(
316+
f"witness metadata count {len(metadata_rows)} does not match "
317+
f"{len(oracle_rows)} parsed SQL statement(s)"
318+
)
319+
320+
witnesses = []
321+
unlinked_witnesses = []
322+
seen_ids = set()
323+
for index, (oracle_row, metadata_row) in enumerate(
324+
zip(oracle_rows, metadata_rows)
325+
):
326+
_validate_oracle_witness_row(oracle_row, index)
327+
_validate_metadata_row(metadata_row, index, target_major)
328+
329+
witness_id = metadata_row["id"]
330+
if witness_id in seen_ids:
331+
raise ValueError(f"duplicate witness ID {witness_id!r}")
332+
seen_ids.add(witness_id)
333+
334+
expected_node = metadata_row["expected_oracle_node"]
335+
actual_node = oracle_row["oracle_node"]
336+
if actual_node != expected_node:
337+
raise ValueError(
338+
f"witness {witness_id!r}: oracle node differs from "
339+
f"expected {expected_node!r}: {actual_node!r}"
340+
)
341+
342+
witness = copy.deepcopy(oracle_row)
343+
witness.update(copy.deepcopy(metadata_row))
344+
witness["oracle_node"] = actual_node
345+
witness["result"] = oracle_row["result"]
346+
witnesses.append(witness)
347+
if not witness["structural_feature_ids"]:
348+
unlinked_witnesses.append(witness_id)
349+
350+
return {
351+
"witnesses": witnesses,
352+
"unlinked_witnesses": sorted(unlinked_witnesses),
353+
}
354+
355+
356+
def load_reviewed_witnesses(
357+
sql_path,
358+
metadata_path,
359+
runner_path,
360+
*,
361+
target_major,
362+
branch,
363+
commit,
364+
):
365+
oracle_rows = parse_witnesses_with_runner(
366+
sql_path,
367+
runner_path,
368+
branch=branch,
369+
commit=commit,
370+
)
371+
metadata_rows = _read_json_array(metadata_path, "witness metadata")
372+
return validate_witnesses(
373+
oracle_rows,
374+
metadata_rows,
375+
target_major=target_major,
376+
)
377+
378+
188379
def write_ci_cases(path, cases):
189380
records = _records_by_id(cases, "CI case", require_sql=True)
190381
output = []
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
[]

0 commit comments

Comments
 (0)