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/config.py
ADDED
|
@@ -0,0 +1,477 @@
|
|
|
1
|
+
"""Complexity-dial configuration for query generation.
|
|
2
|
+
|
|
3
|
+
Parallel to `schema.SchemaConfig` / `schema_config_for_complexity`,
|
|
4
|
+
but for the query generator rather than the schema generator. The
|
|
5
|
+
philosophy is the same: a 0..10 dial maps onto a fully-specified
|
|
6
|
+
config, but the config can also be hand-built when the preset doesn't
|
|
7
|
+
match the use case.
|
|
8
|
+
|
|
9
|
+
Two separate knobs:
|
|
10
|
+
|
|
11
|
+
* `feature_flags` GATE which features are *available*. Lower
|
|
12
|
+
complexity unlocks fewer features (e.g. LEFT JOIN appears only
|
|
13
|
+
at c >= 4). The generator checks `feature in cfg.feature_flags`
|
|
14
|
+
before considering the feature at all.
|
|
15
|
+
|
|
16
|
+
* Probability constants flavor *how often* available features
|
|
17
|
+
fire. These don't slide with complexity — keeping them constant
|
|
18
|
+
means dialing complexity changes the *shape* of generated queries
|
|
19
|
+
(more joins, more select items, deeper exprs) rather than just
|
|
20
|
+
the rate at which fixed features show up.
|
|
21
|
+
|
|
22
|
+
This split keeps "what does c=5 look like?" predictable: the dial
|
|
23
|
+
unlocks feature buckets; the buckets fire at fixed rates.
|
|
24
|
+
"""
|
|
25
|
+
from __future__ import annotations
|
|
26
|
+
|
|
27
|
+
from dataclasses import dataclass
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
# Recognized feature-flag names. Exposed as a constant so the
|
|
31
|
+
# generator's `if 'left_join' in flags:` checks are typo-resistant
|
|
32
|
+
# at the test level.
|
|
33
|
+
#
|
|
34
|
+
# Using string literals (not an Enum) keeps the feature_flags set
|
|
35
|
+
# trivial to dump/diff in test failures: a frozenset of plain
|
|
36
|
+
# strings prints readably under pytest -v. The cost is the
|
|
37
|
+
# named-constant discipline below — but mypy + the __all__ export
|
|
38
|
+
# keep typos from compiling.
|
|
39
|
+
FEATURE_INNER_JOIN = "inner_join"
|
|
40
|
+
FEATURE_LEFT_JOIN = "left_join"
|
|
41
|
+
FEATURE_WHERE = "where"
|
|
42
|
+
FEATURE_ORDER_BY = "order_by"
|
|
43
|
+
FEATURE_LIMIT = "limit"
|
|
44
|
+
FEATURE_AGGREGATE = "aggregate"
|
|
45
|
+
FEATURE_HAVING = "having"
|
|
46
|
+
FEATURE_SCALAR_SUBQUERY = "scalar_subquery"
|
|
47
|
+
FEATURE_EXISTS = "exists"
|
|
48
|
+
FEATURE_IN_SUBQUERY = "in_subquery"
|
|
49
|
+
FEATURE_DERIVED_TABLE = "derived_table"
|
|
50
|
+
FEATURE_LATERAL = "lateral"
|
|
51
|
+
FEATURE_CTE = "cte"
|
|
52
|
+
FEATURE_WINDOW_FUNCTION = "window_function"
|
|
53
|
+
FEATURE_SET_OP = "set_op"
|
|
54
|
+
FEATURE_RECURSIVE_CTE = "recursive_cte"
|
|
55
|
+
FEATURE_GROUPING_SET = "grouping_set"
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
@dataclass(frozen=True)
|
|
59
|
+
class ComplexityConfig:
|
|
60
|
+
"""Tunables for the query generator.
|
|
61
|
+
|
|
62
|
+
Frozen for the same reason the rest of the model is frozen:
|
|
63
|
+
immutability prevents one branch of generation from accidentally
|
|
64
|
+
mutating a config field in a way that affects siblings, and the
|
|
65
|
+
config is hashable for any future caching.
|
|
66
|
+
"""
|
|
67
|
+
|
|
68
|
+
# ---- Structural caps -------------------------------------------------
|
|
69
|
+
max_expr_depth: int
|
|
70
|
+
"""Maximum expression-tree depth. The expression generator
|
|
71
|
+
decrements `depth_remaining` on each recursive call; at zero,
|
|
72
|
+
only leaf productions (column ref, literal) are allowed."""
|
|
73
|
+
|
|
74
|
+
max_from_items: int
|
|
75
|
+
"""Maximum number of tables in the FROM clause. The generator
|
|
76
|
+
picks 1..max inclusive; with N > 1 items, joins (or comma cross
|
|
77
|
+
products) connect them."""
|
|
78
|
+
|
|
79
|
+
max_select_items: int
|
|
80
|
+
"""Maximum number of expressions in the SELECT list."""
|
|
81
|
+
|
|
82
|
+
max_group_by_items: int
|
|
83
|
+
"""Maximum number of expressions in the GROUP BY clause for an
|
|
84
|
+
aggregating query. Only consulted when the query is chosen to
|
|
85
|
+
aggregate; for non-aggregating queries this field is ignored."""
|
|
86
|
+
|
|
87
|
+
max_subquery_depth: int
|
|
88
|
+
"""Maximum nesting depth of subqueries (scalar / EXISTS / IN). The
|
|
89
|
+
outermost query is depth 0; a subquery inside it is depth 1; a
|
|
90
|
+
subquery inside that is depth 2. Counted separately from
|
|
91
|
+
`max_expr_depth` because subquery nesting is a different
|
|
92
|
+
rationing dimension — a deep `a + b * c + ...` expression
|
|
93
|
+
shouldn't share a budget with `(SELECT ... WHERE x IN (SELECT ...))`.
|
|
94
|
+
Set to 0 to disable subqueries even when the feature flag is set.
|
|
95
|
+
|
|
96
|
+
Also covers CTE inner-SELECT nesting: each CTE definition's body
|
|
97
|
+
consumes one level of subquery budget."""
|
|
98
|
+
|
|
99
|
+
max_ctes_per_with: int
|
|
100
|
+
"""Maximum number of CTE definitions in a single WITH clause.
|
|
101
|
+
Capped low (1..3 across the dial) so the WITH list stays readable
|
|
102
|
+
in eyeballed output. The combinatorial blow-up in nested-query
|
|
103
|
+
complexity comes from max_subquery_depth, not from CTE count."""
|
|
104
|
+
|
|
105
|
+
# ---- Feature gates ---------------------------------------------------
|
|
106
|
+
feature_flags: frozenset[str]
|
|
107
|
+
"""Set of FEATURE_* identifiers that are unlocked at this dial
|
|
108
|
+
setting. The generator silently skips features whose flag is
|
|
109
|
+
absent (no error — just doesn't generate them)."""
|
|
110
|
+
|
|
111
|
+
# ---- Per-clause firing probabilities --------------------------------
|
|
112
|
+
p_where: float
|
|
113
|
+
"""P(emit WHERE), conditional on FEATURE_WHERE being set."""
|
|
114
|
+
|
|
115
|
+
p_order_by: float
|
|
116
|
+
"""P(emit ORDER BY), conditional on FEATURE_ORDER_BY being set."""
|
|
117
|
+
|
|
118
|
+
p_limit: float
|
|
119
|
+
"""P(emit LIMIT), conditional on FEATURE_LIMIT being set."""
|
|
120
|
+
|
|
121
|
+
p_explicit_join: float
|
|
122
|
+
"""P(use INNER/LEFT JOIN syntax over comma cross product) when
|
|
123
|
+
multiple FROM items exist. Comma joins remain valid PG syntax,
|
|
124
|
+
but explicit joins read more like real SQL."""
|
|
125
|
+
|
|
126
|
+
p_left_join_when_explicit: float
|
|
127
|
+
"""When emitting an explicit join AND FEATURE_LEFT_JOIN is set,
|
|
128
|
+
P(LEFT JOIN over INNER JOIN). At 0.0, every explicit join is
|
|
129
|
+
INNER even if LEFT is unlocked."""
|
|
130
|
+
|
|
131
|
+
p_aggregate_query: float
|
|
132
|
+
"""P(this query aggregates), conditional on FEATURE_AGGREGATE
|
|
133
|
+
being set. Kept under 0.5 so non-aggregate queries remain the
|
|
134
|
+
common case — both paths get exercised by the headline parse
|
|
135
|
+
sweep."""
|
|
136
|
+
|
|
137
|
+
p_having: float
|
|
138
|
+
"""P(emit HAVING), conditional on the query already aggregating
|
|
139
|
+
AND FEATURE_HAVING being set. HAVING without aggregation is a
|
|
140
|
+
PG syntax error; the gate inside gen_select enforces this."""
|
|
141
|
+
|
|
142
|
+
p_derived_table_in_from: float
|
|
143
|
+
"""P(a given FROM item becomes a derived table), conditional on
|
|
144
|
+
FEATURE_DERIVED_TABLE being set. Kept under 0.5 so most FROM
|
|
145
|
+
items remain base tables — derived tables are a flavor, not the
|
|
146
|
+
dominant shape, of real SQL."""
|
|
147
|
+
|
|
148
|
+
p_lateral_when_derived: float
|
|
149
|
+
"""P(LATERAL prefix on a derived table), conditional on
|
|
150
|
+
FEATURE_LATERAL being set AND the derived table being past the
|
|
151
|
+
first FROM position (LATERAL on the first FROM item is a no-op
|
|
152
|
+
— nothing precedes it to reference)."""
|
|
153
|
+
|
|
154
|
+
p_with_clause: float
|
|
155
|
+
"""P(emit a WITH clause), conditional on FEATURE_CTE being set.
|
|
156
|
+
Kept under 0.5 so non-CTE queries remain the common case —
|
|
157
|
+
both paths get exercised by the headline parse sweep."""
|
|
158
|
+
|
|
159
|
+
p_cte_in_from: float
|
|
160
|
+
"""P(a given FROM item becomes a CteRef rather than a base/derived
|
|
161
|
+
table), conditional on at least one CTE being visible in scope.
|
|
162
|
+
The flag check happens AFTER has_visible_ctes; useless without
|
|
163
|
+
visible CTEs."""
|
|
164
|
+
|
|
165
|
+
p_partition_by: float
|
|
166
|
+
"""P(a window spec includes a PARTITION BY clause). Most real-
|
|
167
|
+
world window calls partition; the default is biased high."""
|
|
168
|
+
|
|
169
|
+
p_order_by_in_window: float
|
|
170
|
+
"""P(a window spec includes an ORDER BY). Some functions (lag,
|
|
171
|
+
lead, first_value, last_value, row_number) are typically used
|
|
172
|
+
with ORDER BY; the default is biased high."""
|
|
173
|
+
|
|
174
|
+
max_partition_by_items: int
|
|
175
|
+
"""Cap on the number of expressions in PARTITION BY. Kept low
|
|
176
|
+
(1..2) so window specs stay readable."""
|
|
177
|
+
|
|
178
|
+
max_order_by_in_window_items: int
|
|
179
|
+
"""Cap on the number of items in a window's ORDER BY. Same
|
|
180
|
+
rationale as max_partition_by_items."""
|
|
181
|
+
|
|
182
|
+
p_window_frame: float
|
|
183
|
+
"""P(a window spec includes an explicit frame clause like
|
|
184
|
+
`ROWS BETWEEN N PRECEDING AND CURRENT ROW`). Independent of
|
|
185
|
+
p_partition_by / p_order_by_in_window — frames can appear
|
|
186
|
+
even on `OVER ()`. Real SQL most often omits the frame
|
|
187
|
+
(PG's default RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT
|
|
188
|
+
ROW is usually fine), so this is biased moderate-low."""
|
|
189
|
+
|
|
190
|
+
max_derived_table_columns: int
|
|
191
|
+
"""Cap on the number of columns in a derived-table subquery
|
|
192
|
+
(`(SELECT c1, c2, c3 FROM ...) AS dt`). Capped low so most
|
|
193
|
+
derived tables stay single-column (the most common real-world
|
|
194
|
+
shape) but multi-column shapes get exercised regularly."""
|
|
195
|
+
|
|
196
|
+
max_cte_columns: int
|
|
197
|
+
"""Cap on the number of columns in a non-recursive CTE
|
|
198
|
+
(`WITH cte AS (SELECT c1, c2, c3 FROM ...)`). Same rationale
|
|
199
|
+
as max_derived_table_columns. Recursive CTEs stay single-
|
|
200
|
+
column for now — the multi-column recursive case requires both
|
|
201
|
+
arms to agree on each column type, more bookkeeping than the
|
|
202
|
+
polish item warrants."""
|
|
203
|
+
|
|
204
|
+
max_set_op_arms: int
|
|
205
|
+
"""Cap on the number of arms in a UNION/INTERSECT/EXCEPT.
|
|
206
|
+
Capped low (2..3) so output stays readable; nested set ops
|
|
207
|
+
are deferred to milestone 8+."""
|
|
208
|
+
|
|
209
|
+
p_set_op_query: float
|
|
210
|
+
"""P(this query is a SetOp rather than a plain Select), gated
|
|
211
|
+
on FEATURE_SET_OP. Kept under 0.5 so plain SELECTs remain the
|
|
212
|
+
common case."""
|
|
213
|
+
|
|
214
|
+
p_nested_set_op_arm: float
|
|
215
|
+
"""P(an arm of a SetOp is itself a SetOp). When non-zero,
|
|
216
|
+
produces shapes like `A UNION (B INTERSECT C)` with the
|
|
217
|
+
parens mandated by PG's set-op precedence (INTERSECT binds
|
|
218
|
+
tighter than UNION/EXCEPT). Each nested SetOp consumes another
|
|
219
|
+
`subquery_depth_remaining`, so arbitrary nesting is impossible."""
|
|
220
|
+
|
|
221
|
+
p_grouping_set: float
|
|
222
|
+
"""P(an aggregate query's GROUP BY uses a ROLLUP/CUBE/GROUPING
|
|
223
|
+
SETS extension instead of a flat column list), gated on
|
|
224
|
+
FEATURE_GROUPING_SET. When firing, the grouped columns are
|
|
225
|
+
wrapped in one of the three constructs uniformly. Plain
|
|
226
|
+
column-list GROUP BY remains the common case so output stays
|
|
227
|
+
readable."""
|
|
228
|
+
|
|
229
|
+
p_set_op_all: float
|
|
230
|
+
"""When emitting a set op, P(use the ALL variant — `UNION ALL`
|
|
231
|
+
etc.). The non-ALL form is the deduplicating variant."""
|
|
232
|
+
|
|
233
|
+
p_recursive_when_cte: float
|
|
234
|
+
"""P(a CTE definition is recursive), conditional on
|
|
235
|
+
FEATURE_RECURSIVE_CTE being set. Recursive CTEs are advanced;
|
|
236
|
+
keeping this modest avoids drowning normal-CTE coverage."""
|
|
237
|
+
|
|
238
|
+
# ---- Expression-generator weights -----------------------------------
|
|
239
|
+
leaf_bias: float
|
|
240
|
+
"""Exponent applied to `depth_remaining / max_expr_depth` when
|
|
241
|
+
weighting recursive productions. With leaf_bias=1.0 the bias
|
|
242
|
+
decays linearly with depth; >1.0 favors leaves more aggressively
|
|
243
|
+
(shorter expression trees on average); <1.0 favors recursion
|
|
244
|
+
(taller but spiker trees)."""
|
|
245
|
+
|
|
246
|
+
func_call_weight: float
|
|
247
|
+
binary_op_weight: float
|
|
248
|
+
column_ref_weight: float
|
|
249
|
+
literal_weight: float
|
|
250
|
+
aggregate_call_weight: float
|
|
251
|
+
"""Weight for an aggregate function call when it's a candidate
|
|
252
|
+
in the expression generator (only when allow_aggregates is True
|
|
253
|
+
and we're not already inside an aggregate). Tuned modestly so
|
|
254
|
+
aggregates appear regularly in HAVING and inside expression
|
|
255
|
+
arguments without dominating the candidate pool."""
|
|
256
|
+
|
|
257
|
+
window_call_weight: float
|
|
258
|
+
"""Weight for a window-style function call (`func(args) OVER (...)`)
|
|
259
|
+
when it's a candidate in the expression generator. Only eligible
|
|
260
|
+
when allow_window=True (set by gen_select for SELECT-list expr
|
|
261
|
+
generation) and not in_window (no nested windows)."""
|
|
262
|
+
|
|
263
|
+
scalar_subquery_weight: float
|
|
264
|
+
"""Weight for a scalar subquery `(SELECT col FROM ...)` candidate
|
|
265
|
+
in the expression generator. Eligible at any target type when
|
|
266
|
+
the feature is unlocked and there's subquery-depth budget."""
|
|
267
|
+
|
|
268
|
+
exists_weight: float
|
|
269
|
+
"""Weight for an `[NOT ]EXISTS (...)` candidate. Only eligible
|
|
270
|
+
when the target type is BOOL and FEATURE_EXISTS is set."""
|
|
271
|
+
|
|
272
|
+
in_subquery_weight: float
|
|
273
|
+
"""Weight for an `<expr> [NOT ]IN (...)` candidate. Only eligible
|
|
274
|
+
when the target type is BOOL and FEATURE_IN_SUBQUERY is set."""
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
def query_config_for_complexity(complexity: int) -> ComplexityConfig:
|
|
278
|
+
"""Map a 0..10 complexity dial onto a ComplexityConfig.
|
|
279
|
+
|
|
280
|
+
The dial unlocks features in stages:
|
|
281
|
+
c == 0: trivial SELECT col FROM t1, no joins, no clauses
|
|
282
|
+
c >= 1: WHERE, INNER JOIN unlock; max_from_items grows past 1
|
|
283
|
+
c >= 2: ORDER BY, LIMIT unlock
|
|
284
|
+
c >= 3: AGGREGATE unlocks (queries can do GROUP BY)
|
|
285
|
+
c >= 4: LEFT JOIN, SCALAR SUBQUERY unlock
|
|
286
|
+
c >= 5: HAVING, EXISTS, IN-SUBQUERY, DERIVED TABLE unlock
|
|
287
|
+
c >= 6: LATERAL unlocks (only meaningful with derived tables)
|
|
288
|
+
c >= 7: CTE unlocks (WITH clauses)
|
|
289
|
+
c >= 8: WINDOW FUNCTION unlocks (`func() OVER (...)`)
|
|
290
|
+
c >= 9: SET OP unlocks (UNION/INTERSECT/EXCEPT [ALL])
|
|
291
|
+
c >= 10: RECURSIVE CTE unlocks (`WITH RECURSIVE ...`)
|
|
292
|
+
|
|
293
|
+
Why complexity 0 emits flat `SELECT col FROM t`: the dial is
|
|
294
|
+
intentionally a strict superset — every shape at level N also
|
|
295
|
+
appears at every level > N. That property lets a user "lower
|
|
296
|
+
the dial until the bug reproduces" without changing the qualitative
|
|
297
|
+
character of what's generated. If c=0 produced something different
|
|
298
|
+
in spirit from c=1, the dial would become a discontinuous
|
|
299
|
+
classifier rather than a continuous knob.
|
|
300
|
+
|
|
301
|
+
Why structural caps double-ish: each step roughly doubles the
|
|
302
|
+
surface area the generator can hit at that level (`c // 2`,
|
|
303
|
+
`c // 3`, etc.). Linear scaling would leave the top of the dial
|
|
304
|
+
underpowered relative to the unlocked features; exponential would
|
|
305
|
+
blow up parse-tree size and slow the test suite. Halving steps
|
|
306
|
+
are a deliberate sweet spot for a <1s test suite.
|
|
307
|
+
"""
|
|
308
|
+
# `c` is clamped — callers occasionally pass complexity from CLI
|
|
309
|
+
# input or fuzz seeds and we'd rather degrade smoothly than raise.
|
|
310
|
+
c = max(0, min(10, complexity))
|
|
311
|
+
|
|
312
|
+
# The notch ordering below is load-bearing: features land in the
|
|
313
|
+
# order they're typically built up in real SQL — predicates first
|
|
314
|
+
# (WHERE, joins), then result shaping (ORDER BY, LIMIT), then
|
|
315
|
+
# aggregation, then the more advanced clausal features (HAVING,
|
|
316
|
+
# subqueries), then CTEs, then windows, then set ops. Reordering
|
|
317
|
+
# changes which fixtures fire at each c-level and breaks
|
|
318
|
+
# determinism guarantees that callers may depend on for golden
|
|
319
|
+
# output. Add new features at the END of an existing notch when
|
|
320
|
+
# possible; only introduce new notches when a feature is genuinely
|
|
321
|
+
# gated on something earlier being unlocked first.
|
|
322
|
+
flags: set[str] = set()
|
|
323
|
+
if c >= 1:
|
|
324
|
+
flags.add(FEATURE_WHERE)
|
|
325
|
+
flags.add(FEATURE_INNER_JOIN)
|
|
326
|
+
if c >= 2:
|
|
327
|
+
flags.add(FEATURE_ORDER_BY)
|
|
328
|
+
flags.add(FEATURE_LIMIT)
|
|
329
|
+
if c >= 3:
|
|
330
|
+
flags.add(FEATURE_AGGREGATE)
|
|
331
|
+
if c >= 4:
|
|
332
|
+
flags.add(FEATURE_LEFT_JOIN)
|
|
333
|
+
flags.add(FEATURE_SCALAR_SUBQUERY)
|
|
334
|
+
if c >= 5:
|
|
335
|
+
# HAVING is grouped with the subquery batch because both rely
|
|
336
|
+
# on the c=3 AGGREGATE unlock to produce useful output: HAVING
|
|
337
|
+
# only fires on aggregating queries, and subqueries gain a lot
|
|
338
|
+
# of expressive power once the aggregate path is live.
|
|
339
|
+
flags.add(FEATURE_HAVING)
|
|
340
|
+
flags.add(FEATURE_EXISTS)
|
|
341
|
+
flags.add(FEATURE_IN_SUBQUERY)
|
|
342
|
+
flags.add(FEATURE_DERIVED_TABLE)
|
|
343
|
+
if c >= 6:
|
|
344
|
+
# LATERAL is a no-op without DERIVED_TABLE; the c=6 placement
|
|
345
|
+
# ensures derived tables exist by the time LATERAL unlocks.
|
|
346
|
+
flags.add(FEATURE_LATERAL)
|
|
347
|
+
if c >= 7:
|
|
348
|
+
flags.add(FEATURE_CTE)
|
|
349
|
+
if c >= 8:
|
|
350
|
+
flags.add(FEATURE_WINDOW_FUNCTION)
|
|
351
|
+
if c >= 9:
|
|
352
|
+
flags.add(FEATURE_SET_OP)
|
|
353
|
+
if c >= 10:
|
|
354
|
+
# Top-of-dial features: recursive CTEs require the plain CTE
|
|
355
|
+
# machinery (c=7) to already work, and grouping sets require
|
|
356
|
+
# the AGGREGATE machinery (c=3). Both are kept off until the
|
|
357
|
+
# full dial is in use because they exercise narrow PG paths
|
|
358
|
+
# that swamp other coverage if they fire too readily.
|
|
359
|
+
flags.add(FEATURE_RECURSIVE_CTE)
|
|
360
|
+
flags.add(FEATURE_GROUPING_SET)
|
|
361
|
+
|
|
362
|
+
return ComplexityConfig(
|
|
363
|
+
# Structural caps grow with complexity. // 2 / // 3 / // 4
|
|
364
|
+
# keep the numbers small so debug output stays human-readable
|
|
365
|
+
# even at the high end.
|
|
366
|
+
max_expr_depth=1 + c // 2, # 1 .. 6
|
|
367
|
+
max_from_items=1 + c // 3, # 1 .. 4
|
|
368
|
+
max_select_items=1 + c // 2, # 1 .. 6
|
|
369
|
+
max_group_by_items=1 + c // 4, # 1 .. 3
|
|
370
|
+
# Subquery depth: 0 below the unlock notch (subqueries gated
|
|
371
|
+
# by feature flag so this doesn't fire anyway), then grows
|
|
372
|
+
# slowly. At c=10 we allow 3 levels of nesting — deeper than
|
|
373
|
+
# most hand-written SQL. The (c - 4) // 3 step is intentionally
|
|
374
|
+
# gentler than max_expr_depth's c // 2: subquery nesting
|
|
375
|
+
# combinatorially explodes parse-tree size in a way that flat
|
|
376
|
+
# expression depth does not, so the budget grows in coarser
|
|
377
|
+
# steps to keep generated SQL human-readable at the top of dial.
|
|
378
|
+
max_subquery_depth=max(0, 1 + (c - 4) // 3) if c >= 4 else 0,
|
|
379
|
+
# CTEs are capped low (1..3) so the WITH list stays readable
|
|
380
|
+
# — depth complexity comes from max_subquery_depth, not count.
|
|
381
|
+
max_ctes_per_with=max(1, 1 + (c - 7) // 2) if c >= 7 else 1,
|
|
382
|
+
feature_flags=frozenset(flags),
|
|
383
|
+
# Constant probabilities — see module docstring on why.
|
|
384
|
+
p_where=0.7,
|
|
385
|
+
p_order_by=0.5,
|
|
386
|
+
p_limit=0.3,
|
|
387
|
+
p_explicit_join=0.85,
|
|
388
|
+
p_left_join_when_explicit=0.3,
|
|
389
|
+
# Aggregating is intentionally a minority outcome (~35%) so
|
|
390
|
+
# the non-aggregate path keeps getting exercised by every
|
|
391
|
+
# parametrized parse sweep. HAVING fires roughly half the
|
|
392
|
+
# time when the query is already aggregating.
|
|
393
|
+
p_aggregate_query=0.35,
|
|
394
|
+
p_having=0.4,
|
|
395
|
+
# Derived tables: same minority-outcome reasoning. ~25% of
|
|
396
|
+
# FROM items become derived; LATERAL fires ~half the time
|
|
397
|
+
# when a derived table is past the first FROM position.
|
|
398
|
+
p_derived_table_in_from=0.25,
|
|
399
|
+
p_lateral_when_derived=0.5,
|
|
400
|
+
# CTEs: ~30% of queries get a WITH clause when the feature
|
|
401
|
+
# is unlocked; once a CTE exists in scope, a given FROM item
|
|
402
|
+
# has a ~25% chance of being a CteRef (vs base/derived).
|
|
403
|
+
p_with_clause=0.3,
|
|
404
|
+
p_cte_in_from=0.25,
|
|
405
|
+
# Window specs: most window calls use PARTITION BY and
|
|
406
|
+
# ORDER BY in real SQL. Capped low (1..2) for readability.
|
|
407
|
+
p_partition_by=0.7,
|
|
408
|
+
p_order_by_in_window=0.7,
|
|
409
|
+
max_partition_by_items=2,
|
|
410
|
+
max_order_by_in_window_items=2,
|
|
411
|
+
# Frames: ~30% of windows get an explicit frame. Most real
|
|
412
|
+
# SQL omits frames (PG default is fine), but generating them
|
|
413
|
+
# exercises the printer's frame path and PG's grammar
|
|
414
|
+
# validation more thoroughly.
|
|
415
|
+
p_window_frame=0.3,
|
|
416
|
+
# Derived tables and non-recursive CTEs: 1..3 columns. Cap
|
|
417
|
+
# is low enough that single-column remains the most common
|
|
418
|
+
# output (the plurality, not the dominant majority); the
|
|
419
|
+
# multi-column shape gets exercised in maybe a third of
|
|
420
|
+
# derived/CTE emissions.
|
|
421
|
+
max_derived_table_columns=3,
|
|
422
|
+
max_cte_columns=3,
|
|
423
|
+
# Set ops: 2 arms at unlock notch, growing to 3 at c=10.
|
|
424
|
+
# Probability ~0.25 keeps SetOps a minority outcome so
|
|
425
|
+
# plain SELECTs stay the common case.
|
|
426
|
+
max_set_op_arms=2 if c < 10 else 3,
|
|
427
|
+
p_set_op_query=0.25,
|
|
428
|
+
p_set_op_all=0.5,
|
|
429
|
+
# Nested set ops: low probability so most SetOps remain
|
|
430
|
+
# the simple flat shape. When it does fire, exercises the
|
|
431
|
+
# printer's mandatory-paren path for sub-SetOp arms.
|
|
432
|
+
p_nested_set_op_arm=0.2,
|
|
433
|
+
# Grouping sets: moderate probability. When firing, the
|
|
434
|
+
# whole GROUP BY uses one of ROLLUP/CUBE/GROUPING SETS
|
|
435
|
+
# (uniformly chosen), exercising the multi-grouping path
|
|
436
|
+
# that PG's planner has its own handling for.
|
|
437
|
+
p_grouping_set=0.3,
|
|
438
|
+
# Recursive CTEs: ~30% of CTEs become recursive when the
|
|
439
|
+
# feature is unlocked. Both forms (recursive and plain) get
|
|
440
|
+
# exercised by every CTE-eligible parse sweep.
|
|
441
|
+
p_recursive_when_cte=0.3,
|
|
442
|
+
# Expression shape. Weights are relative — only ratios matter.
|
|
443
|
+
# Column refs are weighted heavily because they're what makes
|
|
444
|
+
# the query reference the schema at all; without enough column
|
|
445
|
+
# refs, output is just literal arithmetic.
|
|
446
|
+
leaf_bias=1.0,
|
|
447
|
+
func_call_weight=2.0,
|
|
448
|
+
binary_op_weight=2.0,
|
|
449
|
+
column_ref_weight=4.0,
|
|
450
|
+
literal_weight=1.0,
|
|
451
|
+
aggregate_call_weight=2.0,
|
|
452
|
+
# Subqueries are recursive productions on the same scale as
|
|
453
|
+
# function calls. Modest weights so they appear regularly
|
|
454
|
+
# without dominating — output should still mostly be ordinary
|
|
455
|
+
# column refs and operators.
|
|
456
|
+
scalar_subquery_weight=1.0,
|
|
457
|
+
exists_weight=1.0,
|
|
458
|
+
in_subquery_weight=1.0,
|
|
459
|
+
# Window calls are heavyweight (the OVER clause adds visible
|
|
460
|
+
# text); modest weight keeps them flavorful, not dominant.
|
|
461
|
+
window_call_weight=1.5,
|
|
462
|
+
)
|
|
463
|
+
|
|
464
|
+
|
|
465
|
+
__all__ = [
|
|
466
|
+
"ComplexityConfig",
|
|
467
|
+
"query_config_for_complexity",
|
|
468
|
+
"FEATURE_INNER_JOIN", "FEATURE_LEFT_JOIN",
|
|
469
|
+
"FEATURE_WHERE", "FEATURE_ORDER_BY", "FEATURE_LIMIT",
|
|
470
|
+
"FEATURE_AGGREGATE", "FEATURE_HAVING", "FEATURE_GROUPING_SET",
|
|
471
|
+
"FEATURE_SCALAR_SUBQUERY", "FEATURE_EXISTS", "FEATURE_IN_SUBQUERY",
|
|
472
|
+
"FEATURE_DERIVED_TABLE", "FEATURE_LATERAL",
|
|
473
|
+
"FEATURE_CTE",
|
|
474
|
+
"FEATURE_WINDOW_FUNCTION",
|
|
475
|
+
"FEATURE_SET_OP",
|
|
476
|
+
"FEATURE_RECURSIVE_CTE",
|
|
477
|
+
]
|