use std::collections::HashSet;
use crate::ast::generated::{NodeIdWalk, Visit};
use crate::ast::*;
use crate::parser::{TestDialect, parse_with};
fn collect(statements: &[Statement<NoExt>]) -> Vec<Meta> {
let mut walk = NodeIdWalk::default();
for statement in statements {
walk.visit_statement(statement);
}
walk.metas
}
fn first_duplicate(ids: &[NodeId]) -> Option<NodeId> {
let mut seen = HashSet::with_capacity(ids.len());
ids.iter().copied().find(|id| !seen.insert(*id))
}
fn meta_with_id(id: u32) -> Meta {
Meta::new(Span::new(0, 1), NodeId::new(id).expect("non-zero node id"))
}
const REPRESENTATIVE_CORPUS: &[&str] = &[
"CREATE TABLE t (\
id INT PRIMARY KEY, \
name TEXT NOT NULL DEFAULT 'x', \
n INT GENERATED ALWAYS AS (id + 1) STORED, \
ident BIGINT GENERATED BY DEFAULT AS IDENTITY, \
CONSTRAINT u UNIQUE (name), \
CHECK (id > 0)\
)",
"CREATE TABLE t AS SELECT 1",
"CREATE TEMPORARY TABLE t (id INT) \
WITH (fillfactor = 70) ON COMMIT DROP TABLESPACE pg_default",
"INSERT INTO t (id, name) VALUES (1, DEFAULT), (2, 'b')",
"UPDATE t AS target SET a = 1, b = DEFAULT FROM u WHERE target.id = u.id",
"DELETE FROM t AS target USING u WHERE target.id = u.id",
"VALUES (1, DEFAULT), (DEFAULT, 2)",
"SELECT t.a + 1 AS x, count(DISTINCT a), \
CASE a WHEN 1 THEN b ELSE c END, \
CAST(a AS INTEGER), EXTRACT(year FROM a) \
FROM s.t AS t LEFT JOIN u ON TRUE \
WHERE a IN (1, 2) AND b BETWEEN 1 AND 2 AND EXISTS (SELECT 1) \
ORDER BY a LIMIT 10",
"WITH c AS (SELECT 1) SELECT 1 UNION ALL SELECT 2",
"SELECT * FROM (SELECT 1) AS s",
"BEGIN; SAVEPOINT sp1; ROLLBACK TO SAVEPOINT sp1; RELEASE SAVEPOINT sp1; COMMIT",
"SET TRANSACTION ISOLATION LEVEL READ COMMITTED",
"START TRANSACTION READ ONLY NOT DEFERRABLE",
"GRANT SELECT, INSERT ON t TO alice, bob",
"REVOKE SELECT ON t FROM alice",
"SET search_path = public",
"SET TIME ZONE LOCAL; SET TIME ZONE 'UTC'; SET LOCAL TIME ZONE DEFAULT",
"SET ROLE NONE; SET SESSION AUTHORIZATION admin; SET NAMES utf8 COLLATE utf8_bin",
"SET CONSTRAINTS ALL DEFERRED; SET CONSTRAINTS a, b IMMEDIATE",
"SET SESSION CHARACTERISTICS AS TRANSACTION ISOLATION LEVEL SERIALIZABLE, READ ONLY",
"RESET ALL",
];
#[test]
fn representative_parsed_trees_have_unique_nonzero_node_ids() {
let mut total = 0usize;
for &sql in REPRESENTATIVE_CORPUS {
let parsed = parse_with(sql, TestDialect)
.unwrap_or_else(|err| panic!("representative SQL must parse: {sql:?}: {err:?}"));
let ids: Vec<NodeId> = collect(parsed.statements())
.iter()
.map(|meta| meta.node_id)
.collect();
assert!(!ids.is_empty(), "{sql:?}: traversal collected no node ids");
assert!(
ids.iter().all(|id| id.as_u32() != 0),
"{sql:?}: a node carried a zero id",
);
if let Some(dup) = first_duplicate(&ids) {
panic!(
"{sql:?}: node id {} appears on more than one node — a node was \
built outside make_meta or reused a placeholder id",
dup.as_u32(),
);
}
total += ids.len();
}
assert!(
total > 100,
"corpus exercised only {total} nodes; expected many"
);
}
#[test]
fn representative_parsed_trees_have_non_synthetic_spans() {
let mut total = 0usize;
for &sql in REPRESENTATIVE_CORPUS {
let parsed = parse_with(sql, TestDialect)
.unwrap_or_else(|err| panic!("representative SQL must parse: {sql:?}: {err:?}"));
let spans: Vec<Span> = collect(parsed.statements())
.iter()
.map(|meta| meta.span)
.collect();
assert!(
!spans.is_empty(),
"{sql:?}: traversal collected no node spans"
);
assert!(
spans.iter().all(|span| !span.is_synthetic()),
"{sql:?}: a parsed node reported Span::SYNTHETIC instead of its source range",
);
total += spans.len();
}
assert!(
total > 100,
"corpus exercised only {total} nodes; expected many"
);
}
fn walk_ids(expr: &Expr<NoExt>) -> Vec<NodeId> {
let mut walk = NodeIdWalk::default();
walk.visit_expr(expr);
walk.metas.iter().map(|meta| meta.node_id).collect()
}
#[test]
fn walk_flags_a_node_that_reuses_an_id() {
let reused = NodeId::new(1).expect("non-zero node id");
let ids = walk_ids(&Expr::Literal {
literal: Literal {
kind: LiteralKind::Integer,
meta: meta_with_id(1),
},
meta: meta_with_id(1),
});
assert_eq!(ids.len(), 2, "both Expr and Literal ids are recorded");
assert_eq!(first_duplicate(&ids), Some(reused));
}
#[test]
fn walk_accepts_distinct_ids() {
let ids = walk_ids(&Expr::Literal {
literal: Literal {
kind: LiteralKind::Integer,
meta: meta_with_id(1),
},
meta: meta_with_id(2),
});
assert_eq!(ids.len(), 2);
assert_eq!(first_duplicate(&ids), None);
}