diff --git a/Cargo.lock b/Cargo.lock
index 0882c97e86..a5d4e8ec33 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1148,6 +1148,7 @@ dependencies = [
"polyglot-sql-function-catalogs",
"rayon",
"regex",
+ "rustc-hash",
"serde",
"serde_json",
"sha2",
@@ -1315,6 +1316,7 @@ dependencies = [
"polyglot-sql",
"rayon",
"regex",
+ "rustc-hash",
"serde",
"serde_json",
"sha2",
diff --git a/Cargo.toml b/Cargo.toml
index 1eed898f30..bc0e40fc5b 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -84,6 +84,8 @@ ruff_python_ast = "=0.0.4"
ruff_python_parser = "=0.0.4"
ruff_text_size = "=0.0.4"
# Unix only: `sqb` shares the compute log prune lock with Python's `filelock` through `flock`.
+# Fast deterministic hasher for hot in-memory maps whose iteration order never reaches output.
+rustc-hash = "2"
rustix = { version = "1.1.4", default-features = false, features = ["fs", "std"] }
saphyr-parser = "0.1"
regex = "1"
diff --git a/crates/sqlbuild-analysis/Cargo.toml b/crates/sqlbuild-analysis/Cargo.toml
index d9f8c47650..034ba496ec 100644
--- a/crates/sqlbuild-analysis/Cargo.toml
+++ b/crates/sqlbuild-analysis/Cargo.toml
@@ -17,6 +17,7 @@ polyglot-sql.workspace = true
polyglot-sql-function-catalogs.workspace = true
rayon.workspace = true
regex.workspace = true
+rustc-hash.workspace = true
serde.workspace = true
serde_json.workspace = true
sha2.workspace = true
diff --git a/crates/sqlbuild-analysis/src/compiler/_helpers/sql_tests/extraction.rs b/crates/sqlbuild-analysis/src/compiler/_helpers/sql_tests/extraction.rs
index 99a04ce4a9..ced6725397 100644
--- a/crates/sqlbuild-analysis/src/compiler/_helpers/sql_tests/extraction.rs
+++ b/crates/sqlbuild-analysis/src/compiler/_helpers/sql_tests/extraction.rs
@@ -39,6 +39,19 @@ const SCENARIO_CONTEXT: &str = "SQL scenario";
const DECLARATION_CALLS: [&str; 4] = ["enum", "const", "var", "param"];
/// Longest projection the alias help repeats verbatim.
const ALIAS_HELP_EXPRESSION_LIMIT: usize = 80;
+const FROM_KEYWORD: &str = "FROM";
+const SELECT_LIST_END_KEYWORDS: [&str; 10] = [
+ FROM_KEYWORD,
+ "WHERE",
+ "GROUP",
+ "HAVING",
+ "QUALIFY",
+ "WINDOW",
+ "ORDER",
+ "LIMIT",
+ "OFFSET",
+ "FETCH",
+];
const RESERVED_NAMES: [&str; 5] = [
"__actual",
"__expected__typed",
@@ -777,15 +790,7 @@ fn projection_names(
}
}
}
- let mut end = select_list_from(branch, select_end, syntax)?.unwrap_or(branch.len());
- for keyword in [
- "WHERE", "GROUP", "HAVING", "QUALIFY", "WINDOW", "ORDER", "LIMIT", "OFFSET", "FETCH",
- ] {
- if let Some(position) = find_top_level_clause_keyword(branch, select_end, keyword, syntax)?
- {
- end = end.min(position);
- }
- }
+ let end = select_list_end(branch, select_end, syntax)?.unwrap_or(branch.len());
let expressions = split_top_level(&branch[select_end..end], b',', syntax)?;
if expressions.is_empty() {
return Err(
@@ -1379,20 +1384,32 @@ pub(crate) fn find_top_level_keyword(
})
}
-/// The top-level `FROM` that ends a select list, skipping the `FROM` of `IS [NOT] DISTINCT FROM`.
-fn select_list_from(
+/// The first top-level clause keyword ending a select list, skipping names and `IS DISTINCT FROM`.
+fn select_list_end(
sql: &str,
start: usize,
syntax: &LexicalSyntax,
) -> Result