waxsql 1.0.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.
- waxsql/__init__.py +158 -0
- waxsql/ast.py +757 -0
- waxsql/catalog.py +363 -0
- waxsql/cli.py +888 -0
- waxsql/config.py +477 -0
- waxsql/context.py +255 -0
- waxsql/data.py +99 -0
- waxsql/gen/__init__.py +51 -0
- waxsql/gen/cte.py +367 -0
- waxsql/gen/data/__init__.py +14 -0
- waxsql/gen/data/columns.py +48 -0
- waxsql/gen/data/emit.py +247 -0
- waxsql/gen/data/rows.py +236 -0
- waxsql/gen/data/strategies.py +299 -0
- waxsql/gen/expr.py +723 -0
- waxsql/gen/select.py +831 -0
- waxsql/gen/setop.py +259 -0
- waxsql/gen/subquery.py +397 -0
- waxsql/gen/window.py +398 -0
- waxsql/pretty.py +81 -0
- waxsql/printer.py +688 -0
- waxsql/py.typed +0 -0
- waxsql/schema.py +557 -0
- waxsql/scope.py +391 -0
- waxsql/types.py +187 -0
- waxsql/validate/__init__.py +52 -0
- waxsql/validate/parse.py +194 -0
- waxsql/validate/plan.py +149 -0
- waxsql/validate/syntax.py +87 -0
- waxsql-1.0.0.dist-info/METADATA +746 -0
- waxsql-1.0.0.dist-info/RECORD +35 -0
- waxsql-1.0.0.dist-info/WHEEL +5 -0
- waxsql-1.0.0.dist-info/entry_points.txt +2 -0
- waxsql-1.0.0.dist-info/licenses/LICENSE +21 -0
- waxsql-1.0.0.dist-info/top_level.txt +1 -0
waxsql/__init__.py
ADDED
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
"""waxsql — random PostgreSQL query generator (wax-fruit SQL).
|
|
2
|
+
|
|
3
|
+
Public API surface at 1.0: the schema model, the type system, the
|
|
4
|
+
catalog, the validation modes (SYNTAX / PARSE / PLAN), and the
|
|
5
|
+
query generator.
|
|
6
|
+
|
|
7
|
+
The two headline functions:
|
|
8
|
+
|
|
9
|
+
* `generate_schema(seed, complexity)` — random Schema, deterministic
|
|
10
|
+
in seed.
|
|
11
|
+
* `generate_query(seed, schema, complexity)` — random Query against
|
|
12
|
+
that schema, deterministic in seed (independent RNG stream from
|
|
13
|
+
the schema generator's, so "same schema, different queries"
|
|
14
|
+
works by varying just the query seed).
|
|
15
|
+
|
|
16
|
+
Render queries with `print_query(q)`. Validate at three tiers:
|
|
17
|
+
`waxsql.validate.syntax.check_syntax(sql)` (no DB),
|
|
18
|
+
`waxsql.validate.parse.check_parse(sql, conn)` (live PG, PREPARE),
|
|
19
|
+
or `waxsql.validate.plan.check_plan(sql, conn)` (live PG, EXPLAIN).
|
|
20
|
+
|
|
21
|
+
Role in the system: this module IS the public surface. The `__all__`
|
|
22
|
+
at the bottom is the supported API contract; anything reachable only
|
|
23
|
+
via dotted paths into submodules is implementation detail subject to
|
|
24
|
+
churn. The data generator (`generate_data`) is re-exported here so
|
|
25
|
+
callers don't need to know it lives under `waxsql.data`.
|
|
26
|
+
|
|
27
|
+
Deliberately kept out of `__all__` to hold a small, stable 1.0 contract:
|
|
28
|
+
|
|
29
|
+
* The full AST node vocabulary (`ColumnRef`, `FuncCall`, `Subquery`,
|
|
30
|
+
`WindowSpec`, `CteDef`, ...) lives in `waxsql.ast` — the canonical
|
|
31
|
+
AST module. Import from there to construct or walk a query tree.
|
|
32
|
+
Only `Query` (the `generate_query` return type) and the `Select` /
|
|
33
|
+
`SetOp` shapes its `.select` can hold are surfaced here.
|
|
34
|
+
* The query-generator internals (`GenContext`, `Scope`, `Binding`,
|
|
35
|
+
`ComplexityConfig`, `query_config_for_complexity`, the `FEATURE_*`
|
|
36
|
+
flags) live in `waxsql.context` / `waxsql.scope` / `waxsql.config`.
|
|
37
|
+
They drive generation but are not a stable extension API — and the
|
|
38
|
+
functions that consume them (`gen_select` / `gen_setop`) aren't
|
|
39
|
+
public either, so exporting the context types alone would be
|
|
40
|
+
incoherent.
|
|
41
|
+
"""
|
|
42
|
+
from __future__ import annotations
|
|
43
|
+
|
|
44
|
+
import random
|
|
45
|
+
from importlib.metadata import PackageNotFoundError, version
|
|
46
|
+
from typing import Optional
|
|
47
|
+
|
|
48
|
+
# Only the workflow-facing AST types are surfaced at top level; the rest
|
|
49
|
+
# of the node vocabulary stays in `waxsql.ast` (see module docstring).
|
|
50
|
+
from .ast import Query, Select, SetOp
|
|
51
|
+
from .catalog import Catalog, FuncKind, FuncSig, OpSig, default_catalog
|
|
52
|
+
# query_config_for_complexity and FEATURE_SET_OP are imported for use in
|
|
53
|
+
# generate_query's body below; they are intentionally NOT re-exported
|
|
54
|
+
# (generator internals — see module docstring).
|
|
55
|
+
from .config import FEATURE_SET_OP, query_config_for_complexity
|
|
56
|
+
from .context import GenContext
|
|
57
|
+
from .data import generate_data
|
|
58
|
+
from .gen import gen_select, gen_setop
|
|
59
|
+
from .printer import print_expr, print_query
|
|
60
|
+
from .schema import (
|
|
61
|
+
Column, ForeignKey, Index, Schema, SchemaConfig, Table,
|
|
62
|
+
generate_schema, generate_schema_with_config,
|
|
63
|
+
quote_ident, schema_config_for_complexity,
|
|
64
|
+
)
|
|
65
|
+
from .scope import Scope
|
|
66
|
+
from .types import (
|
|
67
|
+
BOOL, DATE, FLOAT8, INT4, INT8, INTERVAL, JSONB, NUMERIC,
|
|
68
|
+
PgType, SCALAR_TYPES, TEXT, TIMESTAMPTZ, TypeCategory,
|
|
69
|
+
UUID, VARCHAR, array_of, implicitly_castable,
|
|
70
|
+
)
|
|
71
|
+
from .validate import ValidationMode
|
|
72
|
+
|
|
73
|
+
# Version is the single source of truth in pyproject.toml. Read it at
|
|
74
|
+
# import time via importlib.metadata so we never have two constants to
|
|
75
|
+
# keep in sync. The PackageNotFoundError fallback handles the rare
|
|
76
|
+
# "running directly from the source tree without an install" case.
|
|
77
|
+
try:
|
|
78
|
+
__version__ = version("waxsql")
|
|
79
|
+
except PackageNotFoundError: # pragma: no cover
|
|
80
|
+
__version__ = "0.0.0+unknown"
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def generate_query(
|
|
84
|
+
seed: int,
|
|
85
|
+
*,
|
|
86
|
+
schema: Schema,
|
|
87
|
+
complexity: int = 5,
|
|
88
|
+
catalog: Optional[Catalog] = None,
|
|
89
|
+
) -> Query:
|
|
90
|
+
"""Generate a random Query against `schema`, deterministic in `seed`.
|
|
91
|
+
|
|
92
|
+
Same `(seed, schema, complexity, catalog)` always produces an
|
|
93
|
+
identical Query (and identical printed SQL). The query-generator
|
|
94
|
+
RNG is independent of the schema-generator RNG — pass the same
|
|
95
|
+
seed to both for "fully reproducible session", or vary the query
|
|
96
|
+
seed to fuzz queries against a fixed schema.
|
|
97
|
+
|
|
98
|
+
`catalog` defaults to `default_catalog()` if not supplied.
|
|
99
|
+
"""
|
|
100
|
+
cat = catalog if catalog is not None else default_catalog()
|
|
101
|
+
cfg = query_config_for_complexity(complexity)
|
|
102
|
+
# Construct the GenContext exactly once here, at the entry point.
|
|
103
|
+
# Every downstream generator gets a derivative of this ctx via
|
|
104
|
+
# GenContext.descend / descend_subquery / dataclasses.replace —
|
|
105
|
+
# and crucially, every derivative shares this rng instance. That
|
|
106
|
+
# shared mutable rng is what makes (seed, schema, complexity) ↦
|
|
107
|
+
# query a deterministic function: reordering or duplicating the
|
|
108
|
+
# rng-consuming calls anywhere in the generator tree perturbs the
|
|
109
|
+
# output. Resist any urge to fork a fresh Random() inside a
|
|
110
|
+
# downstream generator "for cleanliness"; it would silently break
|
|
111
|
+
# the determinism test and the fuzzer's bug-reproduction guarantee.
|
|
112
|
+
ctx = GenContext(
|
|
113
|
+
rng=random.Random(seed),
|
|
114
|
+
scope=Scope(),
|
|
115
|
+
schema=schema,
|
|
116
|
+
catalog=cat,
|
|
117
|
+
config=cfg,
|
|
118
|
+
depth_remaining=cfg.max_expr_depth,
|
|
119
|
+
# Opt in to the subquery budget; the GenContext default is 0
|
|
120
|
+
# (no subqueries) so callers must explicitly enable them.
|
|
121
|
+
subquery_depth_remaining=cfg.max_subquery_depth,
|
|
122
|
+
)
|
|
123
|
+
# Top-level dispatch: SetOp (UNION/INTERSECT/EXCEPT) when the
|
|
124
|
+
# feature is unlocked AND the dice roll fires; plain Select
|
|
125
|
+
# otherwise. Set-ops live at the OUTERMOST level — the generator
|
|
126
|
+
# doesn't produce set ops inside subqueries or CTE bodies.
|
|
127
|
+
body: Select | SetOp
|
|
128
|
+
if (FEATURE_SET_OP in cfg.feature_flags
|
|
129
|
+
and ctx.rng.random() < cfg.p_set_op_query):
|
|
130
|
+
body = gen_setop(ctx)
|
|
131
|
+
else:
|
|
132
|
+
body = gen_select(ctx)
|
|
133
|
+
return Query(select=body)
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
__all__ = [
|
|
137
|
+
# Types
|
|
138
|
+
"PgType", "TypeCategory",
|
|
139
|
+
"INT4", "INT8", "NUMERIC", "FLOAT8", "TEXT", "VARCHAR", "BOOL",
|
|
140
|
+
"DATE", "TIMESTAMPTZ", "INTERVAL", "UUID", "JSONB",
|
|
141
|
+
"SCALAR_TYPES", "array_of", "implicitly_castable",
|
|
142
|
+
# Schema
|
|
143
|
+
"Schema", "Table", "Column", "ForeignKey", "Index",
|
|
144
|
+
"SchemaConfig", "schema_config_for_complexity",
|
|
145
|
+
"generate_schema", "generate_schema_with_config",
|
|
146
|
+
"quote_ident",
|
|
147
|
+
# Catalog
|
|
148
|
+
"Catalog", "FuncSig", "OpSig", "FuncKind", "default_catalog",
|
|
149
|
+
# Validation
|
|
150
|
+
"ValidationMode",
|
|
151
|
+
# Query result type + the two shapes its `.select` can hold. The rest
|
|
152
|
+
# of the AST node vocabulary lives in `waxsql.ast` (see docstring).
|
|
153
|
+
"Query", "Select", "SetOp",
|
|
154
|
+
# Generation + rendering
|
|
155
|
+
"generate_query", "print_query", "print_expr",
|
|
156
|
+
# Data generator
|
|
157
|
+
"generate_data",
|
|
158
|
+
]
|