rudb_parse/generate.rs
1//! Writing statements out of the rule table, which is the matcher run in the other direction.
2//!
3//! The matcher walks the table over a token vector and decides where each rule started and stopped.
4//! This walks the same table with no tokens in hand and makes them up: at a choice it picks an
5//! alternative, at a repeat it picks a count, at an identifier it asks a catalog for a name. What
6//! comes out is a statement the grammar can produce, which is a much larger set than the statements
7//! anybody has written down.
8//!
9//! It lives in the library rather than in a test because two things outside this crate need it. The
10//! compatibility harness generates statements and runs them through both engines, and it depends on
11//! `rudb` and on nothing else, so anything it cannot reach through the facade is a hole in the
12//! facade. `spec/sql/duckdb/10-generation-and-fuzzing.md` section 10.2 is the argument for building
13//! it at all.
14//!
15//! Three things keep it from producing either a novel or the same four tokens forever.
16//!
17//! Every node has a cost, which is the smallest number of tokens it can be written down in, and it
18//! is a fixpoint over the table computed once for the process. A choice weights its alternatives by
19//! that cost, so the cheap ones are picked more often and the expression grammar does not run away
20//! down its own left hand side. It is also what guarantees termination: once a statement is over
21//! budget or a rule has come round too often, every choice takes its cheapest alternative, every
22//! optional is skipped and every repeat goes round once, and the cheapest expansion of anything is
23//! finite by construction.
24//!
25//! The recursion bound counts how many times one rule is on the path rather than how long the path
26//! is, which is not the same thing in this grammar and the difference is not subtle. The expression
27//! rules are a chain of about twenty, one per precedence level, and a plain `a + b` walks the whole
28//! chain, so a depth bound tight enough to stop nesting is spent before it reaches a leaf and every
29//! leaf comes out as the cheapest literal there is. The first version of this had one and wrote
30//! several hundred expressions without a single column reference in any of them.
31//!
32//! Names come from a catalog rather than from a pool of letters, and one statement draws its
33//! columns from one table. Neither of those makes the statement mean anything, since a PEG walk has
34//! no idea what a scope is, but both of them mean a generated statement has a real chance of
35//! binding rather than dying on the first name, and a statement that dies on the first name tests
36//! the tokenizer and nothing else.
37//!
38//! What it does not promise is that everything it writes parses. Ordered choice is the reason: this
39//! can pick the fifth alternative of a choice and write text the matcher settles on the second
40//! alternative of, and then the rest of the sequence has nothing to match against. That is a
41//! property of every PEG generator and not a bug here. The share that parses is measured rather
42//! than assumed, by `the_generator_writes_statements_that_parse`, and the interesting inputs are
43//! the ones that do not, because our answer and DuckDB's answer on those is exactly the level two
44//! statement number the harness reports.
45
46use std::sync::OnceLock;
47
48use rudb_common::{Error, Result};
49
50use crate::generated::keywords::KEYWORDS;
51use crate::generated::rules::{CHILDREN, NODES, RULES, SYMBOLS};
52use crate::matcher::SUGGESTIONS;
53use crate::rules::{Node, Op, Suggestion};
54
55/// A node with no finite expansion, which is what a rule that can only refer to itself comes out
56/// as. Nothing is ever generated from one.
57const UNREACHABLE: u32 = u32::MAX;
58
59/// The rule a statement is written from when nothing says otherwise.
60const START: &str = "Statement";
61
62/// How many tokens a statement gets before every remaining decision takes the cheap way out.
63const BUDGET: u32 = 60;
64
65/// How many times one rule may be on the path from the root before the same thing happens.
66const REPEATS: u32 = 3;
67
68/// How many rules may be on that path at once, whatever they are.
69///
70/// This one is not about the shape of the output. The walk is Rust recursion, so a grammar that
71/// went round a cycle of rules that write nothing would run out of thread stack rather than
72/// producing a bad statement, and a number that no real statement comes near is cheap insurance.
73const STACK: u32 = 512;
74
75/// Where a name position gets its name.
76///
77/// Owned strings rather than borrowed ones, because the useful catalog is the one a harness reads
78/// off a live database rather than the one written here, and the default is only what makes the
79/// tests runnable and the examples readable.
80#[derive(Debug, Clone)]
81pub struct Catalog {
82 /// The tables, with their columns. One statement picks one of these and takes its column names
83 /// from it, so `SELECT a FROM t` is far likelier than `SELECT a FROM u`.
84 pub tables: Vec<Table>,
85 pub functions: Vec<String>,
86 pub table_functions: Vec<String>,
87 pub types: Vec<String>,
88 pub schemas: Vec<String>,
89 pub catalogs: Vec<String>,
90 pub pragmas: Vec<String>,
91 pub settings: Vec<String>,
92 /// File names, which are the one name position where a single quoted string is what a person
93 /// would write, so these carry their quotes.
94 pub files: Vec<String>,
95 pub variables: Vec<String>,
96}
97
98/// One table in the catalog.
99#[derive(Debug, Clone)]
100pub struct Table {
101 pub name: String,
102 pub columns: Vec<String>,
103}
104
105impl Default for Catalog {
106 fn default() -> Self {
107 Self {
108 tables: vec![
109 Table {
110 name: "t".into(),
111 columns: ["a", "b", "c"].iter().map(|name| (*name).into()).collect(),
112 },
113 Table {
114 name: "u".into(),
115 columns: ["x", "y"].iter().map(|name| (*name).into()).collect(),
116 },
117 ],
118 functions: names(&["abs", "length", "upper", "count", "coalesce"]),
119 table_functions: names(&["range", "generate_series"]),
120 types: names(&["INTEGER", "VARCHAR", "DOUBLE", "BOOLEAN", "DATE"]),
121 schemas: names(&["main"]),
122 catalogs: names(&["memory"]),
123 pragmas: names(&["database_list", "show_tables"]),
124 settings: names(&["threads", "memory_limit"]),
125 files: names(&["'data.parquet'", "'out.csv'"]),
126 variables: names(&["v"]),
127 }
128 }
129}
130
131fn names(from: &[&str]) -> Vec<String> {
132 from.iter().map(|name| (*name).to_string()).collect()
133}
134
135/// Numbers a `Number` position can be filled with.
136///
137/// All of them are positive, because a minus sign is a token of its own and a literal carrying one
138/// would be two tokens where the grammar asked for one.
139const NUMBERS: [&str; 7] = ["0", "1", "2", "42", "1.5", "1e3", "9223372036854775807"];
140
141/// Strings a `String` position can be filled with, including an escaped quote and a character
142/// outside ASCII, which are the two shapes a tokenizer gets wrong.
143const STRINGS: [&str; 4] = ["'a'", "''", "'it''s'", "'é'"];
144
145/// Operators for the generic `Operator` node, which is only ever the multi character ones.
146///
147/// Every single character operator in the language is spelled by a rule of its own, and the ones
148/// the grammar spells for itself are refused by `OperatorMatcher`, so this is the set that is left.
149const OPERATORS: [&str; 5] = ["||", "<<", ">>", "@>", "&&"];
150
151/// A statement writer.
152///
153/// Cheap to build and cheap to clone, and a run is a seed, so the same generator with the same seed
154/// writes the same statement on any machine and in any order.
155#[derive(Debug, Clone)]
156pub struct Generator {
157 catalog: Catalog,
158 budget: u32,
159 repeats: u32,
160}
161
162impl Default for Generator {
163 fn default() -> Self {
164 Self { catalog: Catalog::default(), budget: BUDGET, repeats: REPEATS }
165 }
166}
167
168impl Generator {
169 /// A generator over the default catalog.
170 #[must_use]
171 pub fn new() -> Self {
172 Self::default()
173 }
174
175 /// A generator over a catalog somebody else built, which is the case that matters.
176 #[must_use]
177 pub fn with_catalog(catalog: Catalog) -> Self {
178 Self { catalog, ..Self::default() }
179 }
180
181 /// How many tokens to write before finishing as cheaply as possible.
182 #[must_use]
183 pub fn budget(mut self, tokens: u32) -> Self {
184 self.budget = tokens.max(1);
185 self
186 }
187
188 /// How many times one rule may appear on the path from the root before the same thing happens.
189 ///
190 /// One is every statement written with no nesting in it at all. Three is a couple of levels of
191 /// subquery and of arithmetic, which is where the interesting shapes are.
192 #[must_use]
193 pub fn repeats(mut self, times: u32) -> Self {
194 self.repeats = times.max(1);
195 self
196 }
197
198 /// One statement, from this seed.
199 ///
200 /// # Panics
201 ///
202 /// Never, in the sense that matters: the rule it writes from is one of the 1088 in the table
203 /// and `every_rule_in_the_table_can_be_written_down` says every one of those has a finite text.
204 #[must_use]
205 pub fn statement(&self, seed: u64) -> String {
206 self.from_rule(START, seed).expect("Statement is a rule")
207 }
208
209 /// One piece of a statement, from a named rule.
210 ///
211 /// For a harness that wants expressions rather than statements, and for the tests, which are
212 /// much easier to read over `Expression` than over `Statement`.
213 ///
214 /// # Errors
215 ///
216 /// There is no rule with that name, or there is and nothing finite can be written from it.
217 pub fn from_rule(&self, rule: &str, seed: u64) -> Result<String> {
218 let index = RULES
219 .binary_search_by(|candidate| candidate.name.cmp(rule))
220 .map_err(|_| Error::parser(format!("no rule named {rule}")))?;
221 let root = RULES[index].root;
222 if costs()[root as usize] == UNREACHABLE {
223 return Err(Error::parser(format!("nothing finite can be written from {rule}")));
224 }
225 let mut run = Run {
226 catalog: &self.catalog,
227 random: Random::new(seed),
228 pieces: Vec::new(),
229 spent: 0,
230 budget: self.budget,
231 repeats: self.repeats,
232 path: vec![0; RULES.len()],
233 stack: 0,
234 tight: false,
235 table: 0,
236 };
237 run.table = run.random.below(self.catalog.tables.len().max(1));
238 run.node(root);
239 Ok(run.pieces.join(" "))
240 }
241}
242
243/// One statement being written.
244struct Run<'a> {
245 catalog: &'a Catalog,
246 random: Random,
247 /// The tokens so far, joined with a space at the end. A space between every pair is not
248 /// prettiness, it is the only way to be sure two tokens do not become a third: `-` then `-`
249 /// written without one is a comment to the end of the line, and `/` then `*` is a comment to
250 /// the end of the statement.
251 pieces: Vec<String>,
252 spent: u32,
253 budget: u32,
254 repeats: u32,
255 /// How many times each rule is on the path from the root to here.
256 path: Vec<u32>,
257 /// How many rules are on that path, which is what keeps the Rust stack out of it.
258 stack: u32,
259 /// Whether the rest of this subtree is being finished as cheaply as it can be.
260 tight: bool,
261 /// Which table of the catalog this statement is about.
262 table: usize,
263}
264
265impl Run<'_> {
266 /// Whether it is time to stop making the statement bigger.
267 fn cheap(&self) -> bool {
268 self.tight || self.spent >= self.budget
269 }
270
271 fn emit(&mut self, text: impl Into<String>) {
272 self.pieces.push(text.into());
273 self.spent += 1;
274 }
275
276 fn node(&mut self, index: u32) {
277 let node = NODES[index as usize];
278 match node.op {
279 Op::Rule => self.rule(node.a, node.b),
280 Op::Sequence => {
281 for child in node.children() {
282 self.node(*child);
283 }
284 }
285 Op::Choice => {
286 let child = self.alternative(node.children(), self.cheap());
287 self.node(child);
288 }
289 Op::Optional => {
290 // One in three rather than one in two. An optional is usually a clause and a
291 // statement is mostly optionals, so an even coin gives every statement half the
292 // clauses in the grammar and nothing else.
293 if !self.cheap() && self.random.chance(3) {
294 self.node(node.a);
295 }
296 }
297 Op::Repeat => {
298 let times = if self.cheap() { 1 } else { self.random.count(1, 3) };
299 for _ in 0..times {
300 self.node(node.a);
301 }
302 }
303 Op::Keyword => {
304 let word = KEYWORDS[node.a as usize].0.to_uppercase();
305 self.emit(word);
306 }
307 Op::KeywordClass => self.keyword_in(node.a),
308 Op::Symbol => self.emit(SYMBOLS[node.a as usize]),
309 Op::Identifier => self.name(SUGGESTIONS[node.a as usize]),
310 Op::Number => {
311 let number = self.random.pick(&NUMBERS);
312 self.emit(number);
313 }
314 Op::String => {
315 let text = self.random.pick(&STRINGS);
316 self.emit(text);
317 }
318 Op::Operator => {
319 let operator = self.random.pick(&OPERATORS);
320 self.emit(operator);
321 }
322 // It is the end of the input, so writing anything at all would be wrong.
323 Op::EndOfInput => {}
324 }
325 }
326
327 /// A reference to a rule, which is the only place the walk can go round in a circle.
328 ///
329 /// The bound is on how many times one rule may be on the path rather than on how long the path
330 /// is. A depth bound sounds like the same thing and is not, because the expression grammar is a
331 /// chain of about twenty rules, one per precedence level, that a single `a + b` goes all the way
332 /// down. A depth of fourteen spends itself somewhere around multiplication and every leaf under
333 /// it comes out as whatever the cheapest literal is, which is exactly what the first version of
334 /// this did: it wrote several hundred expressions and not one of them contained a column.
335 fn rule(&mut self, rule: u32, root: u32) {
336 let index = rule as usize;
337 let was = self.tight;
338 // Sticky, and restored on the way out. Once a subtree is being finished cheaply the whole
339 // of it is, because that is what makes the walk terminate: the cheapest expansion of
340 // anything is finite, and a subtree that went back to choosing freely could go round again.
341 self.tight = was || self.path[index] >= self.repeats || self.stack >= STACK;
342 self.path[index] += 1;
343 self.stack += 1;
344 self.node(root);
345 self.stack -= 1;
346 self.path[index] -= 1;
347 self.tight = was;
348 }
349
350 /// Which alternative of a choice to take.
351 ///
352 /// Cheap means the cheapest, which is what makes the walk terminate. Otherwise the weight is
353 /// `16 / (cost + 1)`, so a two token alternative is picked about five times as often as a
354 /// fifteen token one. Uniform would be wrong in a way that is easy to miss: the expensive
355 /// alternatives in this grammar are the recursive ones, so a fair coin at every choice point
356 /// walks down them nearly every time and a generated statement is a hundred nested casts.
357 fn alternative(&mut self, children: &[u32], cheap: bool) -> u32 {
358 let costs = costs();
359 if cheap {
360 let mut best = children[0];
361 for child in children {
362 if costs[*child as usize] < costs[best as usize] {
363 best = *child;
364 }
365 }
366 return best;
367 }
368 let weights: Vec<u64> =
369 children.iter().map(|child| weight(costs[*child as usize])).collect();
370 let total: u64 = weights.iter().sum();
371 // Every alternative is unreachable, so the choice is too, so nothing picked it and this
372 // cannot happen. Taking the first one is still a better answer than dividing by zero.
373 if total == 0 {
374 return children[0];
375 }
376 let mut pick = self.random.next() % total;
377 for (child, weight) in children.iter().zip(&weights) {
378 if pick < *weight {
379 return *child;
380 }
381 pick -= *weight;
382 }
383 children[children.len() - 1]
384 }
385
386 /// A word from one of the five keyword classes.
387 fn keyword_in(&mut self, mask: u32) {
388 let words = keywords_in(mask as u8);
389 if words.is_empty() {
390 return;
391 }
392 let index = self.random.below(words.len());
393 let word = KEYWORDS[words[index] as usize].0.to_uppercase();
394 self.emit(word);
395 }
396
397 /// A name for whichever of the eleven positions the grammar is at.
398 fn name(&mut self, suggestion: Suggestion) {
399 let catalog = self.catalog;
400 let table = catalog.tables.get(self.table);
401 let pool = match suggestion {
402 Suggestion::TableName => {
403 let name = table.map_or("t", |table| table.name.as_str()).to_string();
404 self.emit(name);
405 return;
406 }
407 Suggestion::ColumnName => {
408 let columns = table.map(|table| table.columns.as_slice()).unwrap_or_default();
409 if columns.is_empty() {
410 self.emit("a");
411 } else {
412 let index = self.random.below(columns.len());
413 let name = columns[index].clone();
414 self.emit(name);
415 }
416 return;
417 }
418 Suggestion::Variable => &catalog.variables,
419 Suggestion::ScalarFunctionName => &catalog.functions,
420 Suggestion::TableFunctionName => &catalog.table_functions,
421 Suggestion::TypeName => &catalog.types,
422 Suggestion::SchemaName => &catalog.schemas,
423 Suggestion::CatalogName => &catalog.catalogs,
424 Suggestion::PragmaName => &catalog.pragmas,
425 Suggestion::SettingName => &catalog.settings,
426 Suggestion::FileName => &catalog.files,
427 };
428 if pool.is_empty() {
429 self.emit("a");
430 return;
431 }
432 let index = self.random.below(pool.len());
433 let name = pool[index].clone();
434 self.emit(name);
435 }
436}
437
438/// How much of a choice's weight an alternative of this cost gets.
439fn weight(cost: u32) -> u64 {
440 if cost == UNREACHABLE {
441 return 0;
442 }
443 (16 / (u64::from(cost) + 1)).max(1)
444}
445
446/// The smallest number of tokens each node can be written down in, computed once for the process.
447///
448/// A fixpoint rather than a walk, because the grammar is recursive and a walk would not terminate.
449/// Costs start at unreachable and only ever fall, and a pass that moves nothing is the answer.
450/// Around ten passes settle this table, which is a few hundred microseconds once ever.
451fn costs() -> &'static [u32] {
452 static COSTS: OnceLock<Box<[u32]>> = OnceLock::new();
453 COSTS.get_or_init(build_costs)
454}
455
456fn build_costs() -> Box<[u32]> {
457 let mut costs = vec![UNREACHABLE; NODES.len()];
458 loop {
459 let mut moved = false;
460 for (index, node) in NODES.iter().enumerate() {
461 let value = cost_of(*node, &costs);
462 if value < costs[index] {
463 costs[index] = value;
464 moved = true;
465 }
466 }
467 if !moved {
468 return costs.into_boxed_slice();
469 }
470 }
471}
472
473fn cost_of(node: Node, costs: &[u32]) -> u32 {
474 match node.op {
475 // Matching the end of the input writes nothing, and an optional that is skipped writes
476 // nothing either, so both are free and neither can make a node unreachable.
477 Op::EndOfInput | Op::Optional => 0,
478 Op::Keyword
479 | Op::KeywordClass
480 | Op::Symbol
481 | Op::Identifier
482 | Op::Number
483 | Op::String
484 | Op::Operator => 1,
485 Op::Rule => costs[node.b as usize],
486 Op::Repeat => costs[node.a as usize],
487 Op::Sequence => CHILDREN[node.a as usize..(node.a + node.b) as usize]
488 .iter()
489 .fold(0, |total, child| total.saturating_add(costs[*child as usize])),
490 Op::Choice => CHILDREN[node.a as usize..(node.a + node.b) as usize]
491 .iter()
492 .map(|child| costs[*child as usize])
493 .min()
494 .unwrap_or(UNREACHABLE),
495 }
496}
497
498/// The words in each keyword class, by index into `KEYWORDS`, built once.
499///
500/// Five classes and 514 words, so this is five short lists and not worth being clever about. The
501/// alternative is scanning the whole table on every `KeywordClass` node, and those are the nodes a
502/// grammar full of keyword lists is mostly made of.
503fn keywords_in(mask: u8) -> &'static [u16] {
504 static BY_CLASS: OnceLock<[Vec<u16>; 8]> = OnceLock::new();
505 let by_class = BY_CLASS.get_or_init(|| {
506 let mut lists: [Vec<u16>; 8] = Default::default();
507 for (index, (_, classes)) in KEYWORDS.iter().enumerate() {
508 for (bit, list) in lists.iter_mut().enumerate() {
509 if classes & (1 << bit) != 0 {
510 list.push(index as u16);
511 }
512 }
513 }
514 lists
515 });
516 // A mask names one class in every node the generator has ever seen, and the lowest set bit is
517 // as good an answer as any if that ever stops being true.
518 match (0..8).find(|bit| mask & (1 << bit) != 0) {
519 Some(bit) => &by_class[bit],
520 None => &[],
521 }
522}
523
524/// A xorshift, so that a seed is the whole of a run.
525///
526/// Not `rand`. This crate has one dependency and the thing being tested here is a grammar walk, so
527/// the quality that matters is that the same seed gives the same statement and not that the bits
528/// pass a statistical suite.
529struct Random(u64);
530
531impl Random {
532 fn new(seed: u64) -> Self {
533 // Zero is the one state a xorshift cannot leave, and seed zero is the one a person types.
534 Self(seed.wrapping_mul(0x2545_f491_4f6c_dd1d) | 1)
535 }
536
537 fn next(&mut self) -> u64 {
538 self.0 ^= self.0 << 13;
539 self.0 ^= self.0 >> 7;
540 self.0 ^= self.0 << 17;
541 self.0
542 }
543
544 fn below(&mut self, bound: usize) -> usize {
545 (self.next() % bound.max(1) as u64) as usize
546 }
547
548 fn count(&mut self, low: usize, high: usize) -> usize {
549 low + self.below(high - low + 1)
550 }
551
552 fn chance(&mut self, one_in: u64) -> bool {
553 self.next() % one_in == 0
554 }
555
556 fn pick<T: Copy>(&mut self, from: &[T]) -> T {
557 from[self.below(from.len())]
558 }
559}
560
561#[cfg(test)]
562mod tests {
563 use super::{Catalog, Generator, Random, Run, Table, UNREACHABLE, costs};
564 use crate::generated::rules::RULES;
565 use crate::matcher::parse_from;
566 use crate::tokenize::tokenize;
567
568 /// How many seeds the tests that measure a share run over when nothing says otherwise.
569 ///
570 /// Three thousand statements and three thousand parses are under a second, so the measurement
571 /// sits in the ordinary test run. `RUDB_GRAMMAR_SEED` sets where a run starts and
572 /// `RUDB_GRAMMAR_SEEDS` how many it does, which is how the numbers in
573 /// `spec/sql/duckdb/10-generation-and-fuzzing.md` section 10.2.1 were taken.
574 const SEEDS: u64 = 3000;
575
576 fn seeds() -> std::ops::RangeInclusive<u64> {
577 let first = setting("RUDB_GRAMMAR_SEED", 1);
578 let count = setting("RUDB_GRAMMAR_SEEDS", SEEDS).max(1);
579 first..=first.saturating_add(count - 1)
580 }
581
582 fn setting(name: &str, fallback: u64) -> u64 {
583 match std::env::var(name) {
584 Ok(text) => text
585 .trim()
586 .parse()
587 .unwrap_or_else(|_| panic!("{name} is {text}, which is not a number")),
588 Err(_) => fallback,
589 }
590 }
591
592 fn statements(generator: &Generator, rule: &str) -> Vec<String> {
593 seeds().map(|seed| generator.from_rule(rule, seed).expect("a rule")).collect()
594 }
595
596 /// The shortest text a rule can be written as, with no seed in it.
597 ///
598 /// A run that is tight from the first node takes the cheapest alternative, skips every optional
599 /// and goes round every repeat once, so there is nothing left for the random numbers to decide
600 /// and the answer is a property of the table.
601 fn cheapest(rule: &str) -> String {
602 let catalog = Catalog::default();
603 let root = RULES[RULES.binary_search_by(|r| r.name.cmp(rule)).expect("a rule")].root;
604 let mut run = Run {
605 catalog: &catalog,
606 random: Random::new(1),
607 pieces: Vec::new(),
608 spent: 0,
609 budget: 1,
610 repeats: 1,
611 path: vec![0; RULES.len()],
612 stack: 0,
613 tight: true,
614 table: 0,
615 };
616 run.node(root);
617 run.pieces.join(" ")
618 }
619
620 #[test]
621 fn the_same_seed_writes_the_same_statement() {
622 let generator = Generator::new();
623 for seed in [1, 2, 99, 10_000] {
624 assert_eq!(generator.statement(seed), generator.statement(seed));
625 }
626 // And two seeds do not, which is the other half of the claim and the one that fails if the
627 // seed stops reaching the walk.
628 assert_ne!(generator.statement(1), generator.statement(2));
629 }
630
631 #[test]
632 fn every_rule_in_the_table_can_be_written_down() {
633 let costs = costs();
634 let unreachable: Vec<&str> = RULES
635 .iter()
636 .filter(|rule| costs[rule.root as usize] == UNREACHABLE)
637 .map(|rule| rule.name)
638 .collect();
639 assert!(unreachable.is_empty(), "no finite text exists for {unreachable:?}");
640 }
641
642 #[test]
643 fn the_cheapest_text_a_rule_has_is_as_long_as_the_cost_table_says() {
644 // The cost table is the whole termination argument, so it is checked against the walk it is
645 // supposed to describe rather than against a second copy of its own arithmetic. A run that
646 // is tight from the first node takes the cheapest alternative, skips every optional and
647 // goes round every repeat once, which is exactly what the cost of a node is defined as, so
648 // the two numbers have to be the same number for all 1088 rules.
649 for rule in &RULES {
650 let text = cheapest(rule.name);
651 let written = if text.is_empty() { 0 } else { text.split(' ').count() as u32 };
652 assert_eq!(written, costs()[rule.root as usize], "{} wrote {text:?}", rule.name);
653 }
654 }
655
656 #[test]
657 fn what_it_writes_always_tokenizes() {
658 // Weaker than parsing and it holds every time rather than nearly every time, because the
659 // pieces are written with a space between every pair and no piece is a fragment of a token.
660 // A failure here is the generator writing something that is not text, which is a different
661 // and worse thing than writing a statement that does not parse.
662 for text in statements(&Generator::new(), "Statement") {
663 tokenize(&text).unwrap_or_else(|error| panic!("{text}\n{error}"));
664 }
665 }
666
667 #[test]
668 fn the_generator_writes_statements_that_parse() {
669 // Not all of them, and the reason is ordered choice: this writes the fifth alternative of a
670 // choice and the matcher settles on the second, which then leaves the rest of the sequence
671 // with nothing to match. The share is measured so that it cannot quietly collapse, and the
672 // floor is well under what it does today, because the number that matters about a generator
673 // is that it keeps working and not that it hits a target.
674 let written = statements(&Generator::new(), "Statement");
675 let parsed =
676 written.iter().filter(|text| parse_from(text, "Statement", true).is_ok()).count();
677 let total = written.len();
678 // Printed rather than only asserted, because this is where the share in section 10.2.1 of
679 // `spec/sql/duckdb/10-generation-and-fuzzing.md` comes from: run the test with a seed count
680 // and `--nocapture` and the number it prints is the number that goes in the document.
681 println!("{parsed} of {total} statements parse");
682 assert!(parsed * 100 / total >= 85, "{parsed} of {total} parse");
683 }
684
685 #[test]
686 fn the_filter_and_the_walk_without_it_agree_on_what_it_writes() {
687 // The FIRST filter claims to be a superset, so turning it off can only ever make the
688 // matcher slower and never make it accept more. Generated statements are the widest set of
689 // inputs there is to ask that over, and they reach parts of the rule table no corpus does.
690 for text in statements(&Generator::new(), "Statement") {
691 let filtered = parse_from(&text, "Statement", true);
692 let whole = parse_from(&text, "Statement", false);
693 assert_eq!(filtered.is_ok(), whole.is_ok(), "the filter changed the answer on {text}");
694 }
695 }
696
697 #[test]
698 fn the_names_it_writes_are_the_catalog_it_was_handed() {
699 // Every word it writes is either a keyword, which comes out upper case, or a name, which
700 // comes out exactly as the catalog spelled it. So a lower case word that is not in the
701 // catalog is a name the generator invented, and a generator that invents names is testing
702 // the tokenizer rather than the binder.
703 let catalog = Catalog {
704 tables: vec![Table { name: "zork".into(), columns: vec!["quux".into()] }],
705 functions: vec!["frob".into()],
706 ..Catalog::default()
707 };
708 let generator = Generator::with_catalog(catalog.clone());
709 let written = statements(&generator, "Statement");
710 let total = written.len();
711 let mut seen_a_column = false;
712 for text in written {
713 for piece in text.split(' ') {
714 if !piece.starts_with(|first: char| first.is_ascii_lowercase()) {
715 continue;
716 }
717 seen_a_column |= piece == "quux";
718 assert!(known(&catalog, piece), "{piece} is not a name the catalog has, in {text}");
719 }
720 }
721 assert!(seen_a_column, "no statement in {total} mentioned a column");
722 }
723
724 fn known(catalog: &Catalog, piece: &str) -> bool {
725 catalog
726 .tables
727 .iter()
728 .any(|table| table.name == piece || table.columns.iter().any(|column| column == piece))
729 || [
730 &catalog.functions,
731 &catalog.table_functions,
732 &catalog.types,
733 &catalog.schemas,
734 &catalog.catalogs,
735 &catalog.pragmas,
736 &catalog.settings,
737 &catalog.files,
738 &catalog.variables,
739 ]
740 .iter()
741 .any(|pool| pool.iter().any(|name| name == piece))
742 }
743
744 #[test]
745 fn a_query_is_what_comes_out_of_the_rule_that_writes_queries() {
746 // `Statement` is a choice of thirty six and a query is one of them, so a caller that wants
747 // queries asks for the rule that writes them rather than filtering what it gets.
748 let generator = Generator::new();
749 let queries = statements(&generator, "SelectStatement");
750 let parsed =
751 queries.iter().filter(|text| parse_from(text, "SelectStatement", true).is_ok()).count();
752 let total = queries.len();
753 println!("{parsed} of {total} queries parse");
754 assert!(parsed * 100 / total >= 80, "{parsed} of {total} parse");
755 assert!(queries.iter().any(|text| text.contains("SELECT")));
756 }
757
758 #[test]
759 fn a_budget_of_one_token_still_writes_a_whole_statement() {
760 // The budget is where to stop growing and not where to stop, so what comes out is the
761 // cheapest whole statement the grammar has rather than a truncated one.
762 // One word, and it is a query, because `SELECT` on its own is a statement in this dialect
763 // and a tie in the cost table goes to the alternative the grammar writes first.
764 assert_eq!(cheapest("Statement"), "SELECT");
765 assert!(parse_from("SELECT", "Statement", true).is_ok());
766 }
767
768 #[test]
769 fn the_shortest_explain_there_is_does_not_parse_and_that_is_the_grammar() {
770 // Worth one test of its own, because it is the clearest example of why the share that
771 // parses is not one. The cheapest thing `EXPLAIN` can be followed by is the `ANALYZE`
772 // statement, so the shortest explain in the grammar is those two words. The matcher reads
773 // them the other way round: `ExplainStatement <- 'EXPLAIN' AnalyzeKeyword? ...` takes the
774 // word as the optional keyword, nothing is left for the statement that has to follow, and a
775 // PEG does not give a taken optional back. DuckDB's parser is this grammar and this
776 // algorithm, so it refuses the same two words for the same reason.
777 assert_eq!(cheapest("ExplainStatement"), "EXPLAIN ANALYZE");
778 assert!(parse_from("EXPLAIN ANALYZE", "ExplainStatement", true).is_err());
779 assert!(parse_from("EXPLAIN ANALYZE SELECT 1", "Statement", true).is_ok());
780 }
781
782 #[test]
783 fn an_unknown_rule_says_so() {
784 let error = Generator::new().from_rule("NoSuchRule", 1).unwrap_err();
785 assert!(error.to_string().contains("no rule named NoSuchRule"), "{error}");
786 }
787}