test: validate complete weighted join SQL results and ordered boundaries

Signed-off-by: discord9 <55937128+discord9@users.noreply.github.com>
This commit is contained in:
discord9
2026-10-10 12:18:39 +08:00
parent e181b9508e
commit 07ad633d38
+333
View File
@@ -0,0 +1,333 @@
#!/usr/bin/env python3
# Copyright 2023 Greptime Team
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Untimed product correctness and EXPLAIN ANALYZE evidence for weightedAVG.
Requires Python 3.11 or newer for the standard-library TOML parser.
"""
import argparse
import json
import math
import os
import secrets
import sys
import tomllib
import urllib.error
import urllib.parse
import urllib.request
from pathlib import Path
def request(url, sql):
endpoint = url.rstrip("/") + "/v1/sql?db=public"
body = urllib.parse.urlencode({"sql": sql}).encode()
req = urllib.request.Request(endpoint, data=body,
headers={"Content-Type": "application/x-www-form-urlencoded"})
try:
with urllib.request.urlopen(req, timeout=120) as response:
if response.status != 200:
raise RuntimeError(f"HTTP {response.status}: {response.read().decode(errors='replace')}")
payload = json.loads(response.read())
except urllib.error.HTTPError as error:
raise RuntimeError(f"HTTP {error.code} for SQL {sql}: {error.read().decode(errors='replace')}") from error
if payload.get("code", 0) != 0:
raise RuntimeError(f"SQL failed: {sql}\n{payload}")
output = payload.get("output")
if not isinstance(output, list) or len(output) != 1 or not isinstance(output[0], dict):
raise RuntimeError(f"Unexpected SQL response envelope: {payload}")
if output[0].get("code", 0) != 0 or output[0].get("error"):
raise RuntimeError(f"SQL item failed: {sql}\n{output[0]}")
if "affectedrows" in output[0]:
return payload
records = output[0].get("records")
if not isinstance(records, dict) or not isinstance(records.get("schema"), dict):
raise RuntimeError(f"Missing records/schema in SQL response: {payload}")
return payload
def result(payload):
records = payload["output"][0]["records"]
columns = records["schema"].get("column_schemas")
rows = records.get("rows")
if not isinstance(columns, list) or not isinstance(rows, list):
raise RuntimeError(f"Malformed SQL records: {records}")
names = [column.get("name") for column in columns]
if any(not isinstance(name, str) for name in names) or len(names) != len(set(names)):
raise RuntimeError(f"Missing or duplicate result columns: {names}")
if any(not isinstance(row, list) or len(row) != len(names) for row in rows):
raise RuntimeError(f"Malformed result rows for columns {names}")
total_rows = records.get("total_rows")
if total_rows is not None and total_rows != len(rows):
raise RuntimeError(f"Incomplete result: total_rows={total_rows}, returned={len(rows)}")
return names, rows
def close(a, b):
if a is None or b is None:
return a is b
try:
a, b = float(a), float(b)
except (TypeError, ValueError):
return False
return math.isfinite(a) and math.isfinite(b) and math.isclose(a, b, rel_tol=1e-12, abs_tol=1e-12)
def compare_full(raw_payload, factor_payload, oracle, label):
names, rows = result(raw_payload)
factor_names, factor_rows = result(factor_payload)
required = ("host", "area", "pairs", "valid", "total", "score")
maps = []
for columns, data in ((names, rows), (factor_names, factor_rows)):
indexes = {column.lower(): i for i, column in enumerate(columns)}
if any(column not in indexes for column in required):
raise RuntimeError(f"{label}: required full-result columns absent: {columns}")
entries = {}
previous = None
for row in data:
key = (row[indexes["host"]], row[indexes["area"]])
order_key = tuple((value is None, "" if value is None else value) for value in key)
if key in entries:
raise AssertionError(f"{label}: duplicate group key {key}")
if previous is not None and order_key < previous:
raise AssertionError(f"{label}: group output not canonical: {previous}, {order_key}")
previous = order_key
entries[key] = tuple(row[indexes[name]] for name in required[2:])
maps.append(entries)
expected = oracle
if expected is not None:
for flavor, actual in zip(("raw", "factor"), maps):
if actual.keys() != expected.keys():
raise AssertionError(f"{label}: {flavor} group keys differ from oracle")
for key, wanted in expected.items():
got = actual[key]
if got[:2] != wanted[:2] or not close(got[2], wanted[2]) or not close(got[3], wanted[3]):
raise AssertionError(f"{label} {flavor} {key}: {got} != oracle {wanted}")
if maps[0].keys() != maps[1].keys():
raise AssertionError(f"{label}: raw/factor group keys differ")
if expected is None:
for key in maps[0]:
left, right = maps[0][key], maps[1][key]
if left[:2] != right[:2] or not close(left[2], right[2]) or not close(left[3], right[3]):
raise AssertionError(f"{label} raw/factor {key}: {left} != {right}")
oracle_rows = None if expected is None else [
{"host": key[0], "area": key[1], "pairs": value[0], "valid": value[1],
"total": value[2], "score": value[3]} for key, value in expected.items()]
return {"name": label, "comparison": "passed", "oracle": oracle_rows,
"raw": raw_payload, "factor": factor_payload}
def corner_rows():
# (host, instance, metric, timestamp-seconds); all rows are explicit oracle inputs.
facts = [("h1", "i1", -2.5, 0), ("h1", "i2", 1.25, 1),
("h1", "i3", -2.5, 2), ("h2", "i4", None, 3),
("h3", "i5", 4.0, 4), (None, "i6", 3.0, 5)]
dims = [("h1", "a", 0.5, 0), ("h1", "a", 2.0, 1),
("h1", "a", 0.5, 2), ("h2", "a", None, 3),
("h1", None, -1.25, 4), ("h4", "a", 3.0, 5),
(None, "a", 2.0, 6)]
return facts, dims
def oracle(facts, dims, predicate):
groups = {}
for host, _, value, ftime in facts:
for dhost, area, weight, dtime in dims:
if host is None or host != dhost or not predicate(host, area, ftime, dtime):
continue
entry = groups.setdefault((host, area), [0, 0, 0.0])
entry[0] += 1
if value is not None and weight is not None:
entry[1] += 1
entry[2] += value * weight
return {key: (pairs, valid, total if valid else None,
total / valid if valid else None)
for key, (pairs, valid, total) in groups.items()}
def insert_sql(table, rows, columns):
def val(value):
if value is None:
return "NULL"
if isinstance(value, str):
return "'" + value.replace("'", "''") + "'"
return str(value)
values = ",".join("(" + ",".join(val(item) for item in row) + ")" for row in rows)
return f"INSERT INTO {table} ({','.join(columns)}) VALUES {values}"
def run_corner(url):
suffix = f"{os.getpid()}_{secrets.token_hex(5)}"
fact_table, dim_table = f"ffx_probe_fact_{suffix}", f"ffx_probe_dim_{suffix}"
facts, dims = corner_rows()
fact_columns = ("host", "instance", "metric_value", "ts")
dim_columns = ("host", "area", "weight", "ts")
created = []
try:
request(url, f"CREATE TABLE {fact_table} (host STRING, instance STRING, metric_value DOUBLE, ts TIMESTAMP(9) TIME INDEX, PRIMARY KEY(host, instance)) ENGINE=mito WITH (append_mode = 'true')")
created.append(fact_table)
request(url, f"CREATE TABLE {dim_table} (host STRING, area STRING, weight DOUBLE, ts TIMESTAMP(9) TIME INDEX, PRIMARY KEY(host, area)) ENGINE=mito WITH (append_mode = 'true')")
created.append(dim_table)
def timestamp(seconds):
return f"2024-01-01 00:00:{seconds:02}"
request(url, insert_sql(fact_table, [(h, i, v, timestamp(t)) for h, i, v, t in facts], fact_columns))
request(url, insert_sql(dim_table, [(h, a, w, timestamp(t)) for h, a, w, t in dims], dim_columns))
tests = {
"wide": (lambda h, a, ft, dt: True, "f.ts >= TIMESTAMP '2024-01-01 00:00:00'", "d.ts >= TIMESTAMP '2024-01-01 00:00:00'"),
"narrow": (lambda h, a, ft, dt: ft >= 1, "f.ts >= TIMESTAMP '2024-01-01 00:00:01'", "d.ts >= TIMESTAMP '2024-01-01 00:00:00'"),
"rare": (lambda h, a, ft, dt: a == "a", "f.ts >= TIMESTAMP '2024-01-01 00:00:00'", "d.ts >= TIMESTAMP '2024-01-01 00:00:00' AND d.area = 'a'"),
"empty": (lambda h, a, ft, dt: False, "f.ts >= TIMESTAMP '2030-01-01 00:00:00'", "d.ts >= TIMESTAMP '2030-01-01 00:00:00'"),
}
evidence = []
top10s = []
for label, (accept, fp, dp) in tests.items():
where = f"{fp} AND {dp}"
raw_sql = (f"SELECT f.host,d.area,COUNT(*) AS pairs,COUNT(f.metric_value*d.weight) AS valid,"
f"SUM(f.metric_value*d.weight) AS total,AVG(f.metric_value*d.weight) AS score "
f"FROM {fact_table} f JOIN {dim_table} d ON f.host=d.host WHERE {where} "
"GROUP BY f.host,d.area ORDER BY f.host NULLS LAST,d.area NULLS LAST")
fact_pred, dim_pred = fp.replace("f.ts", "ts"), dp.replace("d.ts", "ts").replace("d.area", "area")
factor_sql = (f"WITH ff AS (SELECT host,SUM(metric_value) AS fm,COUNT(metric_value) AS fn,COUNT(*) AS fc "
f"FROM {fact_table} WHERE {fact_pred} GROUP BY host), "
f"dd AS (SELECT host,area,SUM(weight) AS dw,COUNT(weight) AS dn,COUNT(*) AS dc "
f"FROM {dim_table} WHERE {dim_pred} GROUP BY host,area) "
"SELECT ff.host,dd.area,ff.fc*dd.dc AS pairs,ff.fn*dd.dn AS valid,ff.fm*dd.dw AS total,"
"CASE WHEN ff.fn=0 OR dd.dn=0 THEN NULL ELSE ff.fm*dd.dw/"
"(CAST(ff.fn AS DOUBLE)*CAST(dd.dn AS DOUBLE)) END AS score "
"FROM ff JOIN dd ON ff.host=dd.host ORDER BY ff.host NULLS LAST,dd.area NULLS LAST")
raw_payload, factor_payload = request(url, raw_sql), request(url, factor_sql)
full_oracle = oracle(facts, dims, accept)
evidence.append(compare_full(raw_payload, factor_payload, full_oracle, label))
top_raw = raw_sql.replace("ORDER BY f.host NULLS LAST,d.area NULLS LAST", "ORDER BY score DESC NULLS LAST,f.host NULLS LAST,d.area NULLS LAST") + " LIMIT 10"
top_factor = factor_sql.replace("ORDER BY ff.host NULLS LAST,dd.area NULLS LAST", "ORDER BY score DESC NULLS LAST,ff.host NULLS LAST,dd.area NULLS LAST") + " LIMIT 10"
top10s.append(compare_top(request(url, top_raw), request(url, top_factor), f"corner/{label}", list(full_oracle.items())))
wide_raw = (f"EXPLAIN ANALYZE VERBOSE SELECT f.host,d.area,AVG(f.metric_value*d.weight) "
f"FROM {fact_table} f JOIN {dim_table} d ON f.host=d.host "
"WHERE f.ts >= TIMESTAMP '2024-01-01 00:00:00' "
"AND f.ts < TIMESTAMP '2024-01-01 00:00:06' "
"AND d.ts >= TIMESTAMP '2024-01-01 00:00:00' "
"AND d.ts < TIMESTAMP '2024-01-01 00:00:07' GROUP BY f.host,d.area")
wide_factor = (f"EXPLAIN ANALYZE VERBOSE WITH ff AS (SELECT host,SUM(metric_value) fm,COUNT(metric_value) fn "
f"FROM {fact_table} WHERE ts >= TIMESTAMP '2024-01-01 00:00:00' "
"AND ts < TIMESTAMP '2024-01-01 00:00:06' GROUP BY host), "
f"dd AS (SELECT host,area,SUM(weight) dw,COUNT(weight) dn FROM {dim_table} "
"WHERE ts >= TIMESTAMP '2024-01-01 00:00:00' AND ts < TIMESTAMP '2024-01-01 00:00:07' "
"GROUP BY host,area) SELECT ff.host,dd.area,ff.fm*dd.dw/"
"(CAST(ff.fn AS DOUBLE)*CAST(dd.dn AS DOUBLE)) FROM ff JOIN dd ON ff.host=dd.host")
explain = {"raw": request(url, wide_raw), "factor": request(url, wide_factor)}
for flavor, payload in explain.items():
names, _ = result(payload)
if "plan" not in [name.lower() for name in names]:
raise RuntimeError(f"EXPLAIN ANALYZE {flavor} returned no plan column: {names}")
return {"checks": evidence, "top10": top10s, "explain_analyze": explain, "tables": [fact_table, dim_table]}
finally:
for table in reversed(created):
request(url, f"DROP TABLE {table}")
def load_case_queries(case_root):
cases = []
for path in sorted(case_root.glob("paper_ffx_*/case.toml")):
case = tomllib.loads(path.read_text())
cases.append((case["case"]["name"], {q["name"]: q["query"] for q in case["scenario"]["queries"]}))
if len(cases) != 3:
raise RuntimeError(f"Expected 3 paper_ffx cases; found {len(cases)}")
return cases
def run_existing(url, case_root):
evidence = []
for case_name, queries in load_case_queries(case_root):
filters = ("wide", "narrow", "rare") if case_name.endswith("balanced") else ("wide",)
case_evidence = {"case": case_name, "checks": []}
for filt in filters:
raw_full, factor_full = request(url, queries[f"raw_full_{filt}"]), request(url, queries[f"factor_full_{filt}"])
check = compare_full(raw_full, factor_full, None, f"{case_name}/{filt}")
names, rows = result(raw_full)
indexes = {name.lower(): i for i, name in enumerate(names)}
groups = {}
for row in rows:
key = (row[indexes["host"]], row[indexes["area"]])
if key in groups:
raise AssertionError(f"{case_name}/{filt}: duplicate full-result key {key}")
groups[key] = (row[indexes["pairs"]], row[indexes["valid"]],
row[indexes["total"]], row[indexes["score"]])
full_order = sorted(groups.items(), key=lambda item: (
item[1][3] is None, -(float(item[1][3]) if item[1][3] is not None else 0.0),
item[0][0] is None, item[0][0] or "", item[0][1] is None, item[0][1] or ""))[:10]
check["top10"] = compare_top(request(url, queries[f"raw_{filt}"]),
request(url, queries[f"factor_{filt}"]),
f"{case_name}/{filt}", full_order)
case_evidence["checks"].append(check)
case_evidence["explain_analyze"] = {
name: request(url, queries[name])
for name in ("diagnostic_explain_raw_wide", "diagnostic_explain_factor_wide")
}
for flavor, payload in case_evidence["explain_analyze"].items():
names, _ = result(payload)
if "plan" not in [name.lower() for name in names]:
raise RuntimeError(f"EXPLAIN ANALYZE {flavor} returned no plan column: {names}")
evidence.append(case_evidence)
return evidence
def compare_top(raw, factor, label, expected_rows):
def fields(payload):
names, rows = result(payload)
indexes = {name.lower(): i for i, name in enumerate(names)}
if not all(name in indexes for name in ("host", "area", "score")):
raise RuntimeError(f"{label}: invalid top schema {names}")
return [(row[indexes["host"]], row[indexes["area"]], row[indexes["score"]]) for row in rows]
left, right = fields(raw), fields(factor)
expected = sorted(expected_rows, key=lambda item: (
item[1][3] is None, -(float(item[1][3]) if item[1][3] is not None else 0.0),
item[0][0] is None, item[0][0] or "", item[0][1] is None, item[0][1] or ""))[:10]
wanted = [(key[0], key[1], values[3]) for key, values in expected]
if len(left) != len(wanted) or len(right) != len(wanted):
raise AssertionError(f"{label}: expected {len(wanted)} top rows, got raw={len(left)} factor={len(right)}")
for flavor, actual in (("raw", left), ("factor", right)):
for index, (got, want) in enumerate(zip(actual, wanted)):
if got[:2] != want[:2] or not close(got[2], want[2]):
raise AssertionError(f"{label}: {flavor} top row {index} {got} != expected {want}")
if index and got[2] is not None and actual[index - 1][2] is not None:
previous = actual[index - 1]
if float(got[2]) > float(previous[2]):
raise AssertionError(f"{label}: {flavor} scores are not descending")
if float(got[2]) == float(previous[2]) and got[:2] < previous[:2]:
raise AssertionError(f"{label}: {flavor} tie keys are not ascending")
return {"comparison": "passed", "expected_rows": wanted, "raw": raw, "factor": factor}
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--url", required=True, help="GreptimeDB HTTP base URL")
parser.add_argument("--out", required=True, type=Path, help="JSON evidence output file")
parser.add_argument("--existing-cases", action="store_true", help="probe already-seeded direct-SST tables")
parser.add_argument("--case-root", type=Path, default=Path("tests/perf/query_cases"))
args = parser.parse_args()
evidence = {"endpoint": args.url, "timing": False}
if args.existing_cases:
evidence["checks"] = run_existing(args.url, args.case_root)
else:
evidence.update(run_corner(args.url))
args.out.parent.mkdir(parents=True, exist_ok=True)
args.out.write_text(json.dumps(evidence, indent=2, allow_nan=False) + "\n")
print(f"Wrote correctness/EXPLAIN evidence to {args.out}")
if __name__ == "__main__":
try:
main()
except Exception as exc:
print(f"probe failed: {exc}", file=sys.stderr)
raise