sqlbenchdag 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- infrastructure/debug_aws.py +40 -0
- scripts/dev/backfill_queries.py +44 -0
- scripts/dev/timestamp_capsule.py +49 -0
- scripts/dev/upgrade_capsule.py +63 -0
- scripts/dev/verify_capsule.py +79 -0
- scripts/dev/verify_portability.py +103 -0
- scripts/tools/_plotlib.py +43 -0
- scripts/tools/analyze_scaling.py +39 -0
- scripts/tools/demo_breach_detection.py +61 -0
- scripts/tools/extract_results.py +57 -0
- scripts/tools/find_kink.py +104 -0
- scripts/tools/gen_experiment_catalog.py +103 -0
- scripts/tools/load_parquet_to_local.py +98 -0
- scripts/tools/plot_execution_modes.py +82 -0
- scripts/tools/plot_scaling.py +91 -0
- scripts/tools/plot_selectivity.py +139 -0
- scripts/tools/plot_threads.py +95 -0
- scripts/tools/regenerate_dashboard.py +58 -0
- scripts/tools/verify_doc_claims.py +88 -0
- sql_benchmarks/__init__.py +3 -0
- sql_benchmarks/api/__init__.py +0 -0
- sql_benchmarks/api/app.py +30 -0
- sql_benchmarks/api/data/__init__.py +0 -0
- sql_benchmarks/api/data/catalog_reader.py +73 -0
- sql_benchmarks/api/data/reader.py +125 -0
- sql_benchmarks/api/logic/__init__.py +0 -0
- sql_benchmarks/api/logic/comparator.py +44 -0
- sql_benchmarks/api/logic/ranker.py +127 -0
- sql_benchmarks/api/models/__init__.py +0 -0
- sql_benchmarks/api/models/catalog.py +22 -0
- sql_benchmarks/api/models/experiments.py +20 -0
- sql_benchmarks/api/models/recommend.py +11 -0
- sql_benchmarks/api/models/results.py +66 -0
- sql_benchmarks/api/routers/__init__.py +0 -0
- sql_benchmarks/api/routers/catalog.py +19 -0
- sql_benchmarks/api/routers/experiments.py +79 -0
- sql_benchmarks/api/routers/recommend.py +22 -0
- sql_benchmarks/api/routers/results.py +51 -0
- sql_benchmarks/assets/__init__.py +4 -0
- sql_benchmarks/assets/benchmark_factory.py +222 -0
- sql_benchmarks/assets/data_factory.py +54 -0
- sql_benchmarks/assets/data_quality.py +91 -0
- sql_benchmarks/assets/ingestion_factory.py +88 -0
- sql_benchmarks/assets/maintenance.py +63 -0
- sql_benchmarks/assets/reporting.py +385 -0
- sql_benchmarks/assets/semantic_gate.py +76 -0
- sql_benchmarks/cli.py +69 -0
- sql_benchmarks/config_loader.py +104 -0
- sql_benchmarks/constants.py +60 -0
- sql_benchmarks/coordinator.py +282 -0
- sql_benchmarks/definitions.py +115 -0
- sql_benchmarks/experiments/templates/experiment_template.yaml +99 -0
- sql_benchmarks/harness.py +84 -0
- sql_benchmarks/jobs.py +8 -0
- sql_benchmarks/partitions.py +17 -0
- sql_benchmarks/plugins/data_sources/declarative_gen.py +211 -0
- sql_benchmarks/plugins/data_sources/local_file_loader.py +62 -0
- sql_benchmarks/plugins/data_sources/tpc_h.py +61 -0
- sql_benchmarks/resources/__init__.py +0 -0
- sql_benchmarks/resources/actian.py +234 -0
- sql_benchmarks/resources/actian_client.py +231 -0
- sql_benchmarks/resources/base_engine.py +58 -0
- sql_benchmarks/resources/base_schema_client.py +104 -0
- sql_benchmarks/resources/duckdb.py +58 -0
- sql_benchmarks/resources/duckdb_client.py +157 -0
- sql_benchmarks/resources/postgres.py +188 -0
- sql_benchmarks/resources/postgres_client.py +184 -0
- sql_benchmarks/resources/quack.py +56 -0
- sql_benchmarks/resources/quack_client.py +169 -0
- sql_benchmarks/resources/typedb_client.py +370 -0
- sql_benchmarks/resources/typedb_engine.py +303 -0
- sql_benchmarks/scripts/__init__.py +0 -0
- sql_benchmarks/scripts/sql/acid_test/duckdb/test.sql +1 -0
- sql_benchmarks/scripts/sql/acid_test/postgres/test.sql +1 -0
- sql_benchmarks/scripts/sql/analytical_wall/actian/analytical_wall.sql +15 -0
- sql_benchmarks/scripts/sql/analytical_wall/analytical_wall.sql.template +19 -0
- sql_benchmarks/scripts/sql/analytical_wall/duckdb/analytical_wall.sql +15 -0
- sql_benchmarks/scripts/sql/analytical_wall/postgres/analytical_wall.sql +15 -0
- sql_benchmarks/scripts/sql/group_by/duckdb/groupby_antipattern.sql +11 -0
- sql_benchmarks/scripts/sql/group_by/duckdb/groupby_recommended_cte.sql +19 -0
- sql_benchmarks/scripts/sql/group_by/postgres/groupby_antipattern.sql +13 -0
- sql_benchmarks/scripts/sql/group_by/postgres/groupby_recommended_pk.sql +11 -0
- sql_benchmarks/scripts/sql/hypergraph/duckdb/q_1hop.sql +5 -0
- sql_benchmarks/scripts/sql/hypergraph/duckdb/q_2hop.sql +6 -0
- sql_benchmarks/scripts/sql/hypergraph/duckdb/q_full.sql +9 -0
- sql_benchmarks/scripts/sql/hypergraph/typedb/q_1hop.sql +4 -0
- sql_benchmarks/scripts/sql/hypergraph/typedb/q_2hop.sql +5 -0
- sql_benchmarks/scripts/sql/hypergraph/typedb/q_full.sql +6 -0
- sql_benchmarks/scripts/sql/joins/duckdb/join_antipattern.sql +5 -0
- sql_benchmarks/scripts/sql/joins/duckdb/join_recommended.sql +4 -0
- sql_benchmarks/scripts/sql/joins/postgres/join_antipattern.sql +5 -0
- sql_benchmarks/scripts/sql/joins/postgres/join_recommended.sql +4 -0
- sql_benchmarks/scripts/sql/null_logic/duckdb/2VL_identity.sql +14 -0
- sql_benchmarks/scripts/sql/null_logic/duckdb/3VL_identity.sql +5 -0
- sql_benchmarks/scripts/sql/null_logic/postgres/2VL_identity.sql +14 -0
- sql_benchmarks/scripts/sql/null_logic/postgres/3VL_identity.sql +5 -0
- sql_benchmarks/scripts/sql/null_sentinel/duckdb/2VL_identity.sql +14 -0
- sql_benchmarks/scripts/sql/null_sentinel/duckdb/3VL_identity.sql +5 -0
- sql_benchmarks/scripts/sql/null_sentinel/duckdb/sentinel_optimization.sql +16 -0
- sql_benchmarks/scripts/sql/null_sentinel/postgres/2VL_identity.sql +14 -0
- sql_benchmarks/scripts/sql/null_sentinel/postgres/3VL_identity.sql +5 -0
- sql_benchmarks/scripts/sql/null_sentinel/postgres/sentinel_optimization.sql +21 -0
- sql_benchmarks/scripts/sql/recursion/duckdb/recursive_cte.sql +26 -0
- sql_benchmarks/scripts/sql/recursion/postgres/recursive_cte.sql +30 -0
- sql_benchmarks/scripts/sql/recursive_graph/duckdb/q_bounded_3hop.sql +17 -0
- sql_benchmarks/scripts/sql/recursive_graph/duckdb/q_fixed_2hop.sql +6 -0
- sql_benchmarks/scripts/sql/recursive_graph/duckdb/q_transitive_closure.sql +17 -0
- sql_benchmarks/scripts/sql/recursive_graph/typedb/q_bounded_3hop.sql +6 -0
- sql_benchmarks/scripts/sql/recursive_graph/typedb/q_fixed_2hop.sql +5 -0
- sql_benchmarks/scripts/sql/recursive_graph/typedb/q_transitive_closure.sql +7 -0
- sql_benchmarks/scripts/sql/security/acid_resilience/duckdb/test.sql +1 -0
- sql_benchmarks/scripts/sql/selectivity/duckdb/q_0_1_percent.sql +1 -0
- sql_benchmarks/scripts/sql/selectivity/duckdb/q_10_percent.sql +1 -0
- sql_benchmarks/scripts/sql/selectivity/duckdb/q_1_percent.sql +1 -0
- sql_benchmarks/scripts/sql/selectivity/duckdb/q_20_percent.sql +1 -0
- sql_benchmarks/scripts/sql/selectivity/duckdb/q_5_percent.sql +1 -0
- sql_benchmarks/scripts/sql/selectivity/duckdb/q_filler.sql +1 -0
- sql_benchmarks/scripts/sql/selectivity/postgres/q_0_1_percent.sql +1 -0
- sql_benchmarks/scripts/sql/selectivity/postgres/q_10_percent.sql +1 -0
- sql_benchmarks/scripts/sql/selectivity/postgres/q_1_percent.sql +1 -0
- sql_benchmarks/scripts/sql/selectivity/postgres/q_20_percent.sql +1 -0
- sql_benchmarks/scripts/sql/selectivity/postgres/q_5_percent.sql +1 -0
- sql_benchmarks/scripts/sql/selectivity/postgres/q_filler.sql +1 -0
- sql_benchmarks/scripts/sql/selectivity/typedb/q_0_1_percent.sql +4 -0
- sql_benchmarks/scripts/sql/selectivity/typedb/q_10_percent.sql +4 -0
- sql_benchmarks/scripts/sql/selectivity/typedb/q_1_percent.sql +4 -0
- sql_benchmarks/scripts/sql/selectivity/typedb/q_20_percent.sql +4 -0
- sql_benchmarks/scripts/sql/selectivity/typedb/q_5_percent.sql +4 -0
- sql_benchmarks/scripts/sql/selectivity/typedb/q_filler.sql +4 -0
- sql_benchmarks/scripts/sql/sort_spill/duckdb/sort_full.sql +25 -0
- sql_benchmarks/scripts/sql/sort_spill/duckdb/sort_wide_rows.sql +25 -0
- sql_benchmarks/scripts/sql/sort_spill/postgres/sort_full.sql +25 -0
- sql_benchmarks/scripts/sql/sort_spill/postgres/sort_wide_rows.sql +26 -0
- sql_benchmarks/scripts/sql/tpch/duckdb/q3_shipping_priority.sql +25 -0
- sql_benchmarks/scripts/sql/tpch/postgres/q3_shipping_priority.sql +25 -0
- sql_benchmarks/utils/__init__.py +0 -0
- sql_benchmarks/utils/common.py +260 -0
- sql_benchmarks/utils/ddl.py +47 -0
- sql_benchmarks/utils/hasher.py +124 -0
- sql_benchmarks/utils/integrity_monitor.py +55 -0
- sql_benchmarks/utils/providers.py +184 -0
- sql_benchmarks/utils/scaling.py +88 -0
- sql_benchmarks/utils/schema.py +118 -0
- sql_benchmarks/utils/semantic_auditor.py +81 -0
- sql_benchmarks/utils/system.py +100 -0
- sql_benchmarks/validator.py +60 -0
- sqlbenchdag-0.1.0.dist-info/METADATA +221 -0
- sqlbenchdag-0.1.0.dist-info/RECORD +153 -0
- sqlbenchdag-0.1.0.dist-info/WHEEL +5 -0
- sqlbenchdag-0.1.0.dist-info/entry_points.txt +2 -0
- sqlbenchdag-0.1.0.dist-info/licenses/LICENSE +202 -0
- sqlbenchdag-0.1.0.dist-info/top_level.txt +4 -0
- venv/bin/activate_this.py +59 -0
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import boto3
|
|
2
|
+
import sys
|
|
3
|
+
|
|
4
|
+
def check_instance_viability():
|
|
5
|
+
# Setup AWS client
|
|
6
|
+
# This will use the credentials from ~/.aws/credentials which the user already set up
|
|
7
|
+
ec2 = boto3.client('ec2', region_name='us-east-1')
|
|
8
|
+
|
|
9
|
+
print("--- DIAGNOSTIC: Checking t2.micro availability ---")
|
|
10
|
+
|
|
11
|
+
try:
|
|
12
|
+
# 1. Describe Offerings to find valid AZs for t2.micro
|
|
13
|
+
response = ec2.describe_instance_type_offerings(
|
|
14
|
+
LocationType='availability-zone',
|
|
15
|
+
Filters=[{'Name': 'instance-type', 'Values': ['t2.micro']}]
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
valid_azs = [o['Location'] for o in response['InstanceTypeOfferings']]
|
|
19
|
+
print(f"Valid AZs for t2.micro: {valid_azs}")
|
|
20
|
+
|
|
21
|
+
if not valid_azs:
|
|
22
|
+
print("CRITICAL: t2.micro is NOT available in this region/account.")
|
|
23
|
+
return False
|
|
24
|
+
|
|
25
|
+
# 2. Check current default subnets
|
|
26
|
+
subnets = ec2.describe_subnets(Filters=[{'Name': 'default-for-az', 'Values': ['true']}])
|
|
27
|
+
print(f"Default Subnets Found: {len(subnets['Subnets'])}")
|
|
28
|
+
|
|
29
|
+
for s in subnets['Subnets']:
|
|
30
|
+
print(f" - Subnet {s['SubnetId']} in {s['AvailabilityZone']}")
|
|
31
|
+
|
|
32
|
+
print("--------------------------------------------------")
|
|
33
|
+
return True
|
|
34
|
+
|
|
35
|
+
except Exception as e:
|
|
36
|
+
print(f"AWS Error: {e}")
|
|
37
|
+
return False
|
|
38
|
+
|
|
39
|
+
if __name__ == "__main__":
|
|
40
|
+
check_instance_viability()
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Backfill queries/ into already-published capsules.
|
|
4
|
+
|
|
5
|
+
For each capsule, embeds the SQL its engines actually ran (selected dialects,
|
|
6
|
+
via utils.common.copy_suite_queries) and re-computes the integrity seal. This
|
|
7
|
+
does NOT re-run any experiment and does NOT change the Experiment ID — the ID
|
|
8
|
+
hashes the suite from source, not the capsule. After running this, the seal's
|
|
9
|
+
sidecars are stale: re-timestamp with timestamp_capsule.py and re-sign the
|
|
10
|
+
release tag (manual).
|
|
11
|
+
|
|
12
|
+
Usage: python scripts/dev/backfill_queries.py <id> [<id> ...]
|
|
13
|
+
"""
|
|
14
|
+
import os
|
|
15
|
+
import sys
|
|
16
|
+
|
|
17
|
+
REPO_ROOT = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
|
18
|
+
sys.path.insert(0, REPO_ROOT)
|
|
19
|
+
|
|
20
|
+
from sql_benchmarks.utils.common import copy_suite_queries
|
|
21
|
+
from sql_benchmarks.utils.hasher import generate_integrity_seal
|
|
22
|
+
|
|
23
|
+
RESULTS = os.path.join(REPO_ROOT, "sql_benchmarks", "experiments", "results")
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def backfill(exp_id: str) -> None:
|
|
27
|
+
capsule = os.path.join(RESULTS, exp_id)
|
|
28
|
+
if not os.path.isdir(capsule):
|
|
29
|
+
print(f"{exp_id}: MISSING — skipped")
|
|
30
|
+
return
|
|
31
|
+
n = copy_suite_queries(capsule)
|
|
32
|
+
seal_path = os.path.join(capsule, "integrity.seal")
|
|
33
|
+
resealed = os.path.exists(seal_path)
|
|
34
|
+
if resealed:
|
|
35
|
+
with open(seal_path, "w") as f:
|
|
36
|
+
f.write(generate_integrity_seal(capsule))
|
|
37
|
+
print(f"{exp_id}: {n} query file(s) embedded; resealed={resealed}")
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
if __name__ == "__main__":
|
|
41
|
+
if len(sys.argv) < 2:
|
|
42
|
+
sys.exit(__doc__)
|
|
43
|
+
for exp_id in sys.argv[1:]:
|
|
44
|
+
backfill(exp_id)
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Timestamp a published capsule's seal with OpenTimestamps.
|
|
4
|
+
|
|
5
|
+
Publication-time action (deliberate, network-required), distinct from sealing
|
|
6
|
+
(automatic, offline, every run). Creates integrity.seal.ots — a trustless,
|
|
7
|
+
Bitcoin-anchored proof that the seal hash existed by a point in time, so a
|
|
8
|
+
result cannot be silently backdated or tampered-then-resealed.
|
|
9
|
+
|
|
10
|
+
Usage: python scripts/dev/timestamp_capsule.py <experiment_id> [<experiment_id> ...]
|
|
11
|
+
Then later (after a Bitcoin block confirms, ~hours):
|
|
12
|
+
python scripts/dev/upgrade_capsule.py <experiment_id> [<experiment_id> ...]
|
|
13
|
+
|
|
14
|
+
Requires: pip install opentimestamps-client
|
|
15
|
+
"""
|
|
16
|
+
import argparse
|
|
17
|
+
import os
|
|
18
|
+
import shutil
|
|
19
|
+
import subprocess
|
|
20
|
+
import sys
|
|
21
|
+
|
|
22
|
+
REPO_ROOT = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def timestamp_capsule(exp_id: str) -> bool:
|
|
26
|
+
seal = os.path.join(REPO_ROOT, "sql_benchmarks", "experiments", "results",
|
|
27
|
+
exp_id, "integrity.seal")
|
|
28
|
+
if not os.path.exists(seal):
|
|
29
|
+
print(f"ERROR: {exp_id} has no integrity.seal — seal the capsule first "
|
|
30
|
+
f"(produced automatically when an experiment finalizes).")
|
|
31
|
+
return False
|
|
32
|
+
if not shutil.which("ots"):
|
|
33
|
+
print("ERROR: `ots` not found. pip install opentimestamps-client")
|
|
34
|
+
return False
|
|
35
|
+
proc = subprocess.run(["ots", "stamp", seal])
|
|
36
|
+
if proc.returncode != 0:
|
|
37
|
+
return False
|
|
38
|
+
print(f"\nStamped: {seal}.ots")
|
|
39
|
+
print("Commit it alongside the capsule. Run `ots upgrade` on it in a few "
|
|
40
|
+
"hours to finalize the Bitcoin attestation.")
|
|
41
|
+
return True
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
if __name__ == "__main__":
|
|
45
|
+
parser = argparse.ArgumentParser(description="OpenTimestamp one or more capsule seals.")
|
|
46
|
+
parser.add_argument("ids", nargs="+", metavar="id", help="8-character Experiment ID(s)")
|
|
47
|
+
args = parser.parse_args()
|
|
48
|
+
results = [timestamp_capsule(exp_id) for exp_id in args.ids]
|
|
49
|
+
sys.exit(0 if all(results) else 1)
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Finalize a capsule's OpenTimestamps proof (the second, later half of timestamping).
|
|
4
|
+
|
|
5
|
+
`timestamp_capsule.py` submits the seal hash to calendar servers and writes a
|
|
6
|
+
*pending* integrity.seal.ots. A few hours later, once a Bitcoin block has
|
|
7
|
+
confirmed the calendar's commitment, run THIS to fetch the on-chain attestation
|
|
8
|
+
and bake it into the .ots so it verifies offline forever — no trust in the
|
|
9
|
+
calendar, no dependency on anyone.
|
|
10
|
+
|
|
11
|
+
Usage: python scripts/dev/upgrade_capsule.py <experiment_id> [<experiment_id> ...]
|
|
12
|
+
Then commit the upgraded proof(s):
|
|
13
|
+
git commit -am "chore: finalize OTS attestation"
|
|
14
|
+
|
|
15
|
+
Requires: pip install opentimestamps-client (provides the `ots` CLI)
|
|
16
|
+
|
|
17
|
+
Resolves paths relative to THIS file's repo, so run it from whichever checkout
|
|
18
|
+
holds the capsule (normally your primary checkout after `git pull`).
|
|
19
|
+
"""
|
|
20
|
+
import argparse
|
|
21
|
+
import os
|
|
22
|
+
import shutil
|
|
23
|
+
import subprocess
|
|
24
|
+
import sys
|
|
25
|
+
|
|
26
|
+
REPO_ROOT = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def upgrade_capsule(exp_id: str) -> bool:
|
|
30
|
+
ots = os.path.join(REPO_ROOT, "sql_benchmarks", "experiments", "results",
|
|
31
|
+
exp_id, "integrity.seal.ots")
|
|
32
|
+
if not os.path.exists(ots):
|
|
33
|
+
print(f"ERROR: {exp_id} has no integrity.seal.ots — stamp it first with "
|
|
34
|
+
f"`python scripts/dev/timestamp_capsule.py {exp_id}`.")
|
|
35
|
+
return False
|
|
36
|
+
if not shutil.which("ots"):
|
|
37
|
+
print("ERROR: `ots` not found. pip install opentimestamps-client")
|
|
38
|
+
return False
|
|
39
|
+
|
|
40
|
+
# `ots upgrade` rewrites the .ots in place when the Bitcoin attestation is
|
|
41
|
+
# ready, and leaves it untouched (exit 0) while still pending.
|
|
42
|
+
proc = subprocess.run(["ots", "upgrade", ots], capture_output=True, text=True)
|
|
43
|
+
out = (proc.stdout + proc.stderr).strip()
|
|
44
|
+
print(out or "(no output)")
|
|
45
|
+
|
|
46
|
+
if "Success" in out or "Got 1 attestation" in out:
|
|
47
|
+
print(f"\nFinalized: {ots}\nCommit it: git commit -am 'chore: finalize OTS attestation for {exp_id}'")
|
|
48
|
+
return True
|
|
49
|
+
if "Pending" in out or "pending" in out:
|
|
50
|
+
print("\nStill pending — the Bitcoin block hasn't confirmed yet. "
|
|
51
|
+
"Try again in a few hours.")
|
|
52
|
+
return False
|
|
53
|
+
# Unknown state: surface it loudly rather than guess.
|
|
54
|
+
print("\nUnexpected `ots upgrade` output — inspect manually before committing.")
|
|
55
|
+
return proc.returncode == 0
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
if __name__ == "__main__":
|
|
59
|
+
parser = argparse.ArgumentParser(description="Finalize one or more capsules' OTS proofs.")
|
|
60
|
+
parser.add_argument("ids", nargs="+", metavar="id", help="8-character Experiment ID(s)")
|
|
61
|
+
args = parser.parse_args()
|
|
62
|
+
results = [upgrade_capsule(exp_id) for exp_id in args.ids]
|
|
63
|
+
sys.exit(0 if all(results) else 1)
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Verify a published result capsule end to end:
|
|
4
|
+
|
|
5
|
+
1. INTEGRITY — recompute the seal over the capsule's files and compare it
|
|
6
|
+
to the stored integrity.seal (detects any byte change).
|
|
7
|
+
2. TIMESTAMP — if integrity.seal.ots is present, verify the OpenTimestamps
|
|
8
|
+
proof (the seal existed at/by a point in time; trustless,
|
|
9
|
+
Bitcoin-anchored). Requires the `ots` client; skipped with a
|
|
10
|
+
notice if unavailable.
|
|
11
|
+
|
|
12
|
+
Authorship (who produced it) is verified separately via the signed git tag:
|
|
13
|
+
git verify-tag <tag>
|
|
14
|
+
|
|
15
|
+
Usage: python scripts/dev/verify_capsule.py <experiment_id>
|
|
16
|
+
"""
|
|
17
|
+
import argparse
|
|
18
|
+
import os
|
|
19
|
+
import shutil
|
|
20
|
+
import subprocess
|
|
21
|
+
import sys
|
|
22
|
+
|
|
23
|
+
# Run standalone from anywhere: put the repo root (3 levels up) on the path.
|
|
24
|
+
REPO_ROOT = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
|
25
|
+
sys.path.insert(0, REPO_ROOT)
|
|
26
|
+
|
|
27
|
+
from sql_benchmarks.utils.hasher import generate_integrity_seal
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def verify_capsule(exp_id: str) -> bool:
|
|
31
|
+
results_dir = os.path.join(REPO_ROOT, "sql_benchmarks", "experiments", "results", exp_id)
|
|
32
|
+
if not os.path.isdir(results_dir):
|
|
33
|
+
print(f"ERROR: Result capsule for {exp_id} not found at {results_dir}")
|
|
34
|
+
return False
|
|
35
|
+
|
|
36
|
+
seal_path = os.path.join(results_dir, "integrity.seal")
|
|
37
|
+
if not os.path.exists(seal_path):
|
|
38
|
+
print(f"WARNING: Capsule {exp_id} is UNSEALED (no integrity.seal).")
|
|
39
|
+
return False
|
|
40
|
+
|
|
41
|
+
# 1. INTEGRITY
|
|
42
|
+
with open(seal_path) as f:
|
|
43
|
+
stored = f.read().strip()
|
|
44
|
+
computed = generate_integrity_seal(results_dir)
|
|
45
|
+
if computed != stored:
|
|
46
|
+
print(f"CRITICAL: integrity violation in {exp_id}!")
|
|
47
|
+
print(f" stored: {stored}")
|
|
48
|
+
print(f" computed: {computed}")
|
|
49
|
+
return False
|
|
50
|
+
print(f"[1/2] INTEGRITY OK — {exp_id} bytes match the seal.")
|
|
51
|
+
|
|
52
|
+
# 2. TIMESTAMP (optional sidecar)
|
|
53
|
+
ots_path = seal_path + ".ots"
|
|
54
|
+
if not os.path.exists(ots_path):
|
|
55
|
+
print(f"[2/2] TIMESTAMP — no integrity.seal.ots (capsule not timestamped).")
|
|
56
|
+
return True
|
|
57
|
+
if not shutil.which("ots"):
|
|
58
|
+
print(f"[2/2] TIMESTAMP — integrity.seal.ots present but `ots` client not "
|
|
59
|
+
f"installed; skipping (pip install opentimestamps-client).")
|
|
60
|
+
return True
|
|
61
|
+
proc = subprocess.run(["ots", "verify", ots_path], capture_output=True, text=True)
|
|
62
|
+
out = (proc.stdout + proc.stderr).lower()
|
|
63
|
+
if "pending" in out:
|
|
64
|
+
print(f"[2/2] TIMESTAMP PENDING — calendars committed (submission time "
|
|
65
|
+
f"attested); run `ots upgrade {ots_path}` after the next Bitcoin "
|
|
66
|
+
f"block to finalize the trustless proof.")
|
|
67
|
+
elif "success" in out or "attests existence" in out or "bitcoin block" in out:
|
|
68
|
+
print(f"[2/2] TIMESTAMP OK — seal anchored to the Bitcoin blockchain.")
|
|
69
|
+
else:
|
|
70
|
+
print(f"[2/2] TIMESTAMP could not be verified:\n{proc.stdout}{proc.stderr}")
|
|
71
|
+
return False
|
|
72
|
+
return True
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
if __name__ == "__main__":
|
|
76
|
+
parser = argparse.ArgumentParser(description="Verify a result capsule (integrity + timestamp).")
|
|
77
|
+
parser.add_argument("id", help="8-character Experiment ID")
|
|
78
|
+
args = parser.parse_args()
|
|
79
|
+
sys.exit(0 if verify_capsule(args.id) else 1)
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
import os
|
|
3
|
+
import sys
|
|
4
|
+
import subprocess
|
|
5
|
+
import importlib
|
|
6
|
+
from datetime import datetime
|
|
7
|
+
|
|
8
|
+
def check_python_version():
|
|
9
|
+
print("[1/5] Checking Python Version...")
|
|
10
|
+
major, minor = sys.version_info[:2]
|
|
11
|
+
if major == 3 and minor >= 10:
|
|
12
|
+
print(f" Python {major}.{minor} detected.")
|
|
13
|
+
return True
|
|
14
|
+
else:
|
|
15
|
+
print(f" ERROR: Python 3.10+ required (Detected {major}.{minor})")
|
|
16
|
+
return False
|
|
17
|
+
|
|
18
|
+
def check_dependencies():
|
|
19
|
+
print("[2/5] Checking Dependencies...")
|
|
20
|
+
required = ["dagster", "polars", "duckdb", "docker", "psutil", "yaml"]
|
|
21
|
+
missing = []
|
|
22
|
+
for lib in required:
|
|
23
|
+
try:
|
|
24
|
+
importlib.import_module(lib if lib != "yaml" else "yaml")
|
|
25
|
+
print(f" {lib} is installed.")
|
|
26
|
+
except ImportError:
|
|
27
|
+
missing.append(lib)
|
|
28
|
+
|
|
29
|
+
if missing:
|
|
30
|
+
print(f" MISSING: {', '.join(missing)}")
|
|
31
|
+
return False
|
|
32
|
+
return True
|
|
33
|
+
|
|
34
|
+
def check_docker():
|
|
35
|
+
print("[3/5] Checking Docker Daemon...")
|
|
36
|
+
try:
|
|
37
|
+
import docker
|
|
38
|
+
client = docker.from_env()
|
|
39
|
+
client.ping()
|
|
40
|
+
print(" Docker daemon is reachable.")
|
|
41
|
+
return True
|
|
42
|
+
except Exception as e:
|
|
43
|
+
print(f" ERROR: Docker daemon not running or unreachable: {e}")
|
|
44
|
+
return False
|
|
45
|
+
|
|
46
|
+
def check_filesystem():
|
|
47
|
+
print("[4/5] Checking Filesystem...")
|
|
48
|
+
root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
|
49
|
+
required_dirs = ["data", "sql_benchmarks/experiments/queue", "dagster_home"]
|
|
50
|
+
|
|
51
|
+
for d in required_dirs:
|
|
52
|
+
path = os.path.join(root, d)
|
|
53
|
+
if not os.path.exists(path):
|
|
54
|
+
try:
|
|
55
|
+
os.makedirs(path, exist_ok=True)
|
|
56
|
+
print(f" Created {d}/")
|
|
57
|
+
except Exception as e:
|
|
58
|
+
print(f" FAILED to create {d}/: {e}")
|
|
59
|
+
return False
|
|
60
|
+
else:
|
|
61
|
+
if os.access(path, os.W_OK):
|
|
62
|
+
print(f" {d}/ is writable.")
|
|
63
|
+
else:
|
|
64
|
+
print(f" {d}/ is NOT writable.")
|
|
65
|
+
return False
|
|
66
|
+
return True
|
|
67
|
+
|
|
68
|
+
def check_dagster_definitions():
|
|
69
|
+
print("[5/5] Checking Dagster Definition Integrity...")
|
|
70
|
+
try:
|
|
71
|
+
# Set environment so definitions load correctly
|
|
72
|
+
root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
|
73
|
+
os.environ["DAGSTER_HOME"] = os.path.join(root, "dagster_home")
|
|
74
|
+
|
|
75
|
+
# Import definitions to see if assets/factories fail to initialize
|
|
76
|
+
sys.path.append(root)
|
|
77
|
+
from sql_benchmarks.definitions import defs
|
|
78
|
+
print(" Dagster definitions loaded successfully.")
|
|
79
|
+
return True
|
|
80
|
+
except Exception as e:
|
|
81
|
+
print(f" ERROR: Definitions failed to load: {e}")
|
|
82
|
+
return False
|
|
83
|
+
|
|
84
|
+
def run_all():
|
|
85
|
+
print(f"--- SQL Benchmarks Portability Audit ({datetime.now().strftime('%Y-%m-%d %H:%M')}) ---")
|
|
86
|
+
results = [
|
|
87
|
+
check_python_version(),
|
|
88
|
+
check_dependencies(),
|
|
89
|
+
check_docker(),
|
|
90
|
+
check_filesystem(),
|
|
91
|
+
check_dagster_definitions()
|
|
92
|
+
]
|
|
93
|
+
|
|
94
|
+
print("-" * 60)
|
|
95
|
+
if all(results):
|
|
96
|
+
print("SUCCESS: Environment is ready for benchmarking.")
|
|
97
|
+
sys.exit(0)
|
|
98
|
+
else:
|
|
99
|
+
print("FAILURE: Please fix the issues above before running.")
|
|
100
|
+
sys.exit(1)
|
|
101
|
+
|
|
102
|
+
if __name__ == "__main__":
|
|
103
|
+
run_all()
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"""Shared display metadata + paths for the figure tools (scripts/tools/plot_*.py).
|
|
2
|
+
|
|
3
|
+
Design-once: an engine's label/color/marker, and the results path, live HERE.
|
|
4
|
+
Every plot_*.py imports from this module, so adding an engine or restyling is a
|
|
5
|
+
single edit — not a copy-paste across four scripts.
|
|
6
|
+
"""
|
|
7
|
+
import os
|
|
8
|
+
import sys
|
|
9
|
+
|
|
10
|
+
REPO_ROOT = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
|
11
|
+
if REPO_ROOT not in sys.path:
|
|
12
|
+
sys.path.insert(0, REPO_ROOT)
|
|
13
|
+
|
|
14
|
+
# Single source of truth for the results path — defined in the package, not redefined here.
|
|
15
|
+
from sql_benchmarks.constants import RESULTS_DIR # noqa: E402
|
|
16
|
+
|
|
17
|
+
# engine -> (display label, hex color, marker). The ONE place to add/restyle an engine.
|
|
18
|
+
# Keys track sql_benchmarks.constants.KNOWN_ENGINES.
|
|
19
|
+
ENGINE_DISPLAY = {
|
|
20
|
+
"duckdb": ("DuckDB in-process", "#2f6f4f", "o"),
|
|
21
|
+
"quack": ("Quack attach (ATTACH + USE)", "#b3402a", "s"),
|
|
22
|
+
"quack_pushdown": ("Quack pushdown (remote.query)", "#3b6ea5", "^"),
|
|
23
|
+
"postgres": ("PostgreSQL", "#7a5ea8", "D"),
|
|
24
|
+
"actian": ("Actian Vector", "#c98a1b", "v"),
|
|
25
|
+
"typedb": ("TypeDB", "#4c4c4c", "P"),
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def style(engine: str):
|
|
30
|
+
"""(label, color, marker) for an engine; falls back to the raw name + default marker."""
|
|
31
|
+
return ENGINE_DISPLAY.get(engine, (engine, None, "o"))
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def bench_string(env: dict, engines=()) -> str:
|
|
35
|
+
"""One-line bench descriptor from a capsule's metadata environment.
|
|
36
|
+
|
|
37
|
+
Lists versions only for the engines the experiment actually used — never
|
|
38
|
+
hardcodes a single engine (so a Postgres-only figure won't claim 'DuckDB').
|
|
39
|
+
"""
|
|
40
|
+
parts = [env.get("machine", ""), f"{env.get('cpu_count_logical', '?')} cores"]
|
|
41
|
+
versions = [f"{e} {env[e]}" for e in engines if env.get(e)]
|
|
42
|
+
tail = ", ".join(versions) if versions else (f"Python {env['python']}" if env.get("python") else "")
|
|
43
|
+
return " · ".join(p for p in (*parts, tail) if p)
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Scaling-law analysis for a results capsule (CLI).
|
|
3
|
+
|
|
4
|
+
Thin wrapper over sql_benchmarks.utils.scaling — the same code the reporting
|
|
5
|
+
asset uses to write scaling.json into every capsule. Reads the sealed capsule,
|
|
6
|
+
so the exponents are reproducible: anyone can rerun this against a published
|
|
7
|
+
capsule and get the same numbers.
|
|
8
|
+
|
|
9
|
+
Usage:
|
|
10
|
+
python scripts/tools/analyze_scaling.py <experiment_id>
|
|
11
|
+
"""
|
|
12
|
+
import os
|
|
13
|
+
import sys
|
|
14
|
+
|
|
15
|
+
# Allow running as a plain script from the repo root (python scripts/tools/...)
|
|
16
|
+
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))))
|
|
17
|
+
|
|
18
|
+
from sql_benchmarks.constants import RESULTS_DIR
|
|
19
|
+
from sql_benchmarks.utils.scaling import analyze_capsule
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def main():
|
|
23
|
+
if len(sys.argv) < 2:
|
|
24
|
+
sys.exit(__doc__)
|
|
25
|
+
exp_id = sys.argv[1]
|
|
26
|
+
report = analyze_capsule(RESULTS_DIR, exp_id)
|
|
27
|
+
if not report:
|
|
28
|
+
sys.exit(f"No row-scaled fragments with timings found for '{exp_id}'.")
|
|
29
|
+
|
|
30
|
+
print(f"Scaling analysis — capsule {exp_id}")
|
|
31
|
+
print(f"{'engine':<16} {'alpha':>7} {'R^2':>6} {'growth':>9} regime")
|
|
32
|
+
print("-" * 56)
|
|
33
|
+
for engine in sorted(report):
|
|
34
|
+
r = report[engine]
|
|
35
|
+
print(f"{engine:<16} {r['alpha']:>7.3f} {r['r2']:>6.3f} {r['growth']:>8.1f}x {r['regime']}")
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
if __name__ == "__main__":
|
|
39
|
+
main()
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import shutil
|
|
3
|
+
import time
|
|
4
|
+
import yaml
|
|
5
|
+
from sql_benchmarks.coordinator import ExperimentCoordinator
|
|
6
|
+
from sql_benchmarks.constants import EXPERIMENTS_DIR
|
|
7
|
+
|
|
8
|
+
def demonstrate_security():
|
|
9
|
+
"""
|
|
10
|
+
Simulates a 'Blueberry Muffin' attack.
|
|
11
|
+
1. Starts a run.
|
|
12
|
+
2. While the coordinator is provisioning, we TAMPER with the code.
|
|
13
|
+
3. We verify the run FAILS to commit.
|
|
14
|
+
"""
|
|
15
|
+
print("\n--- [DEMO] INITIATING BREACH TEST ---")
|
|
16
|
+
|
|
17
|
+
# Setup a dummy experiment
|
|
18
|
+
exp_path = os.path.join(EXPERIMENTS_DIR, "queue", "breach_demo.yaml")
|
|
19
|
+
os.makedirs(os.path.dirname(exp_path), exist_ok=True)
|
|
20
|
+
with open(exp_path, 'w') as f:
|
|
21
|
+
yaml.dump({
|
|
22
|
+
"meta": {"experiment_id": "breach_demo"},
|
|
23
|
+
"execution": {"engines": ["duckdb"], "matrix": {"rows": [100]}}
|
|
24
|
+
}, f)
|
|
25
|
+
|
|
26
|
+
# We need to manually trigger the breach DURING the coordinator life-cycle
|
|
27
|
+
# For this demo, we will wrap the coordinator
|
|
28
|
+
coord = ExperimentCoordinator(exp_path, headless=True)
|
|
29
|
+
|
|
30
|
+
# 1. INITIALIZE (Takes Snapshot)
|
|
31
|
+
print("[1/3] Starting Coordinator...")
|
|
32
|
+
|
|
33
|
+
# We will simulate the 'Run' but inject a modification to a project file
|
|
34
|
+
# right after the harness provisions.
|
|
35
|
+
|
|
36
|
+
target_file = "sql_benchmarks/constants.py"
|
|
37
|
+
original_content = open(target_file, "r").read()
|
|
38
|
+
|
|
39
|
+
try:
|
|
40
|
+
# We start the run
|
|
41
|
+
print(f"[2/3] Tampering with {target_file} during run...")
|
|
42
|
+
with open(target_file, "a") as f:
|
|
43
|
+
f.write("\n# MALICIOUS INJECTION")
|
|
44
|
+
|
|
45
|
+
print("[3/3] Attempting run with tampered code...")
|
|
46
|
+
success = coord.run()
|
|
47
|
+
|
|
48
|
+
if not success:
|
|
49
|
+
print("\nSUCCESS: SYSTEM DETECTED THE BREACH!")
|
|
50
|
+
print("The results were DISCARDED and NOT committed to the repo.")
|
|
51
|
+
else:
|
|
52
|
+
print("\nFAILURE: SYSTEM ALLOWED THE BREACH TO COMMIT!")
|
|
53
|
+
|
|
54
|
+
finally:
|
|
55
|
+
# Restore the file
|
|
56
|
+
with open(target_file, "w") as f:
|
|
57
|
+
f.write(original_content)
|
|
58
|
+
if os.path.exists(exp_path): os.remove(exp_path)
|
|
59
|
+
|
|
60
|
+
if __name__ == "__main__":
|
|
61
|
+
demonstrate_security()
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import polars as pl
|
|
2
|
+
import yaml
|
|
3
|
+
import shutil
|
|
4
|
+
import os
|
|
5
|
+
import sys
|
|
6
|
+
from datetime import datetime
|
|
7
|
+
from dagster import DagsterInstance, DagsterEventType, DagsterRunStatus, RunsFilter
|
|
8
|
+
|
|
9
|
+
# STRICTLY FORCE THE LOCATION THAT WORKED IN DEBUG
|
|
10
|
+
os.environ["DAGSTER_HOME"] = os.path.expanduser("~/.dagster")
|
|
11
|
+
|
|
12
|
+
# Import your constants for file paths
|
|
13
|
+
from sql_benchmarks.constants import ACTIVE_CONFIG_PATH, CONFIG_ARCHIVE_DIR, RESULTS_DIR
|
|
14
|
+
from sql_benchmarks.assets.reporting import parse_fragments_to_records
|
|
15
|
+
|
|
16
|
+
def extract_and_snapshot():
|
|
17
|
+
# 1. READ TARGET ID
|
|
18
|
+
try:
|
|
19
|
+
with open(ACTIVE_CONFIG_PATH, "r") as f:
|
|
20
|
+
config = yaml.safe_load(f)
|
|
21
|
+
target_id = config.get("meta", {}).get("experiment_id")
|
|
22
|
+
print(f"Target Experiment ID: {target_id}")
|
|
23
|
+
except FileNotFoundError:
|
|
24
|
+
print(f"Config not found at {ACTIVE_CONFIG_PATH}")
|
|
25
|
+
return
|
|
26
|
+
|
|
27
|
+
# 2. EXTRACT RECORDS (Via Shared Logic)
|
|
28
|
+
print(f"Scanning fragments for experiment: {target_id}...")
|
|
29
|
+
records = parse_fragments_to_records(target_id)
|
|
30
|
+
|
|
31
|
+
# 3. SAVE RESULTS
|
|
32
|
+
if records:
|
|
33
|
+
df = pl.DataFrame(records)
|
|
34
|
+
|
|
35
|
+
# Deduplicate to keep the latest run of each asset (Logic reused from reporting.py implicitly via output parity)
|
|
36
|
+
df = df.unique(subset=["Asset", "System", "Rows"], keep="last").sort("Rows")
|
|
37
|
+
|
|
38
|
+
# Define Paths
|
|
39
|
+
result_dir = os.path.join(RESULTS_DIR, target_id)
|
|
40
|
+
os.makedirs(result_dir, exist_ok=True)
|
|
41
|
+
os.makedirs(CONFIG_ARCHIVE_DIR, exist_ok=True)
|
|
42
|
+
|
|
43
|
+
csv_path = os.path.join(result_dir, f"results_{target_id}.csv")
|
|
44
|
+
registry_path = os.path.join(CONFIG_ARCHIVE_DIR, f"config_{target_id}.yaml")
|
|
45
|
+
|
|
46
|
+
# Write
|
|
47
|
+
df.write_csv(csv_path)
|
|
48
|
+
shutil.copy(ACTIVE_CONFIG_PATH, registry_path)
|
|
49
|
+
|
|
50
|
+
print(f"\nSUCCESS!")
|
|
51
|
+
print(f" Extracted {len(df)} rows")
|
|
52
|
+
print(f" Saved to: {csv_path}")
|
|
53
|
+
else:
|
|
54
|
+
print(f"\nFAILURE: Scanned fragments but found 0 matches for ID '{target_id}'.")
|
|
55
|
+
|
|
56
|
+
if __name__ == "__main__":
|
|
57
|
+
extract_and_snapshot()
|