From 57b0c206fa1e38dd3ae159bcaeddfe8e82aea372 Mon Sep 17 00:00:00 2001 From: Kevin Longe <273747162+kvlonge@users.noreply.github.com> Date: Sat, 7 Nov 2026 17:22:35 +0000 Subject: [PATCH 1/2] refactor(analysis): read B101 spellings from a recorded table instead of SQL library probes The unknown-function check (B101) asked the SQL library's parser which built-in node each unaccepted call spelling becomes, and took DuckDB's accepted names from the library's function catalogue. The check now reads a table of the 41 rejected spellings, each with its argument count and the spelling the dialect accepts. Every DuckDB row was confirmed unknown to DuckDB 2.0.0. The call scan takes the analysis dialect name, so analysis off the library can run it unchanged. Findings are identical on every DuckDB and Snowflake query of the compiler differential corpus and on every candidate spelling at 0 to 9 arguments. The workspace no longer depends on the library's function-catalogue crate directly; resolved features are unchanged. --- Cargo.lock | 1 - Cargo.toml | 3 - crates/sqlbuild-analysis/Cargo.toml | 1 - .../src/query_analysis/_helpers/engine.rs | 6 +- .../semantic_validation/_helpers/catalog.rs | 13 +- .../_helpers/diagnostics.rs | 7 +- .../_helpers/function_names.rs | 237 +---- .../_helpers/function_spellings.txt | 48 + .../_helpers/snowflake_functions.txt | 993 ------------------ .../main/map_diagnostics.rs | 4 +- .../src/semantic_validation/models.rs | 9 +- .../tests/test_function_names.rs | 48 +- .../tests/test_truth_tests.rs | 3 - .../semantic_validation/tests/test_types.rs | 2 +- .../src/semantic_validation/types.rs | 1 - 15 files changed, 121 insertions(+), 1255 deletions(-) create mode 100644 crates/sqlbuild-analysis/src/semantic_validation/_helpers/function_spellings.txt delete mode 100644 crates/sqlbuild-analysis/src/semantic_validation/_helpers/snowflake_functions.txt diff --git a/Cargo.lock b/Cargo.lock index 7306a408d1..74da5a92fb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4126,7 +4126,6 @@ version = "0.0.0" dependencies = [ "memchr", "polyglot-sql", - "polyglot-sql-function-catalogs", "rayon", "regex", "rustc-hash", diff --git a/Cargo.toml b/Cargo.toml index 9ef850601d..af40942aa5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -93,9 +93,6 @@ polyglot-sql = { git = "https://github.com/sqlstack-io/polyglot.git", rev = "876 "semantic", "stacker", ] } -polyglot-sql-function-catalogs = { git = "https://github.com/sqlstack-io/polyglot.git", rev = "8767d688c5b504ad609645e27ab77a260d91c5a1", default-features = false, features = [ - "dialect-duckdb", -] } blake3 = { version = "1", features = ["rayon"] } # Only sqlbuild-build-features uses these: each enables in the sqb build exactly the features the # extension build resolves for that crate, so both builds share one compile of every common crate. diff --git a/crates/sqlbuild-analysis/Cargo.toml b/crates/sqlbuild-analysis/Cargo.toml index 864c22d7e7..6b0d2063c2 100644 --- a/crates/sqlbuild-analysis/Cargo.toml +++ b/crates/sqlbuild-analysis/Cargo.toml @@ -10,7 +10,6 @@ rust-version.workspace = true [dependencies] memchr.workspace = true polyglot-sql.workspace = true -polyglot-sql-function-catalogs.workspace = true rayon.workspace = true regex.workspace = true rustc-hash.workspace = true diff --git a/crates/sqlbuild-analysis/src/query_analysis/_helpers/engine.rs b/crates/sqlbuild-analysis/src/query_analysis/_helpers/engine.rs index ba731740dd..cde00841a5 100644 --- a/crates/sqlbuild-analysis/src/query_analysis/_helpers/engine.rs +++ b/crates/sqlbuild-analysis/src/query_analysis/_helpers/engine.rs @@ -550,12 +550,8 @@ fn analyze_compact_query_work( if let (Some(sql), Ok(dialect)) = (diagnostic_sql, dialect) && let Some(validation) = result.validation.take() { - let local_probes = crate::semantic_validation::models::FunctionProbes::default(); - let probes = catalog.map_or(&local_probes, |catalog| catalog.function_probes.as_ref()); result.validation = Some(validation.and_then(|value| { - crate::semantic_validation::main::map_diagnostics::map_diagnostics( - &sql, dialect, value, probes, - ) + crate::semantic_validation::main::map_diagnostics::map_diagnostics(&sql, dialect, value) })); } result diff --git a/crates/sqlbuild-analysis/src/semantic_validation/_helpers/catalog.rs b/crates/sqlbuild-analysis/src/semantic_validation/_helpers/catalog.rs index 32fd8e8198..9dae17ad2e 100644 --- a/crates/sqlbuild-analysis/src/semantic_validation/_helpers/catalog.rs +++ b/crates/sqlbuild-analysis/src/semantic_validation/_helpers/catalog.rs @@ -6,7 +6,7 @@ use std::sync::atomic::{AtomicUsize, Ordering}; use std::sync::{Arc, Mutex, PoisonError}; use crate::semantic_validation::models::{ - CatalogInput, Columns, CompactAnalysisJob, DiagnosticRow, FunctionProbes, ProjectCatalog, + CatalogInput, Columns, CompactAnalysisJob, DiagnosticRow, ProjectCatalog, }; use crate::semantic_validation::types::{ BindingRequest, NormalizationRequest, PreparedCompactAnalysis, Relations, @@ -160,7 +160,6 @@ impl ProjectCatalog { tables: HashMap::new(), overrides: Vec::new(), analysis_tables: HashMap::new(), - function_probes: Arc::new(FunctionProbes::default()), analysis_pool: Arc::default(), running_analyses: Arc::default(), }; @@ -182,7 +181,6 @@ impl ProjectCatalog { tables: self.tables.clone(), overrides: Vec::new(), analysis_tables: self.analysis_tables.clone(), - function_probes: Arc::new(FunctionProbes::default()), analysis_pool: Arc::clone(&self.analysis_pool), running_analyses: Arc::clone(&self.running_analyses), }; @@ -285,12 +283,7 @@ impl ProjectCatalog { .map(|(sql, references, overrides)| { let schema = self.schema(&references, overrides)?; let result = self.validate(&sql, &schema)?; - let result = diagnostics::map_diagnostics( - &sql, - self.dialect, - result, - &self.function_probes, - )?; + let result = diagnostics::map_diagnostics(&sql, self.dialect, result)?; Ok(result.errors.into_iter().map(diagnostic_row).collect()) }) .collect::, String>>() @@ -356,7 +349,6 @@ impl ProjectCatalog { tables: HashMap::new(), overrides: Vec::new(), analysis_tables: HashMap::new(), - function_probes: Arc::clone(&self.function_probes), analysis_pool: Arc::clone(&self.analysis_pool), running_analyses: Arc::clone(&self.running_analyses), } @@ -388,7 +380,6 @@ impl ProjectCatalog { tables: HashMap::new(), overrides: Vec::new(), analysis_tables: HashMap::new(), - function_probes: Arc::new(FunctionProbes::default()), analysis_pool: Arc::default(), running_analyses: Arc::default(), } diff --git a/crates/sqlbuild-analysis/src/semantic_validation/_helpers/diagnostics.rs b/crates/sqlbuild-analysis/src/semantic_validation/_helpers/diagnostics.rs index b198ae8a54..2b98a1f64e 100644 --- a/crates/sqlbuild-analysis/src/semantic_validation/_helpers/diagnostics.rs +++ b/crates/sqlbuild-analysis/src/semantic_validation/_helpers/diagnostics.rs @@ -6,7 +6,6 @@ use crate::semantic_validation::_helpers::sqlite_temporal_casts; use crate::semantic_validation::_helpers::truth_tests; use crate::semantic_validation::_helpers::type_locations::locate_type_findings; use crate::semantic_validation::models::DiagnosticRow; -use crate::semantic_validation::models::FunctionProbes; use polyglot_sql::expressions::Select; use polyglot_sql::{ Dialect, DialectType, Expression, ExpressionWalk, ValidationError, ValidationResult, @@ -149,7 +148,6 @@ pub(crate) fn map_diagnostics( sql: &str, dialect: DialectType, result: ValidationResult, - probes: &FunctionProbes, ) -> Result { let mut context = DiagnosticContext { sql, @@ -164,9 +162,7 @@ pub(crate) fn map_diagnostics( errors.push(error); } } - errors.extend(function_names::unsupported_function_errors( - sql, dialect, probes, - )); + errors.extend(function_names::unsupported_function_errors(sql, dialect)); errors.extend(truth_tests::unsupported_truth_test_errors(sql, dialect)); errors.extend(sqlite_temporal_casts::sqlite_temporal_cast_warnings( sql, dialect, @@ -418,7 +414,6 @@ pub(crate) fn binding_diagnostics( valid: false, errors, }, - &FunctionProbes::default(), )?; Ok(result.errors.into_iter().map(diagnostic_row).collect()) } diff --git a/crates/sqlbuild-analysis/src/semantic_validation/_helpers/function_names.rs b/crates/sqlbuild-analysis/src/semantic_validation/_helpers/function_names.rs index ca0337945e..fe9a00ead3 100644 --- a/crates/sqlbuild-analysis/src/semantic_validation/_helpers/function_names.rs +++ b/crates/sqlbuild-analysis/src/semantic_validation/_helpers/function_names.rs @@ -1,28 +1,17 @@ //! Built-in function spellings checked against the spellings the target dialect accepts. -use crate::semantic_validation::models::FunctionProbes; -use crate::semantic_validation::types::ProbeKey; -use polyglot_sql::{Dialect, DialectType, Expression, ValidationError}; -use polyglot_sql_function_catalogs::{CatalogSink, FunctionNameCase, FunctionSignature}; +use polyglot_sql::{DialectType, ValidationError}; use rustc_hash::{FxHashMap, FxHashSet}; use sqlbuild_sqltext::sql_scan::main::comment_end::comment_end; use sqlbuild_sqltext::sql_scan::main::non_code_end::non_code_end; use sqlbuild_sqltext::sql_scan::models::{QuotePolicy, Unclosed}; -use std::mem::Discriminant; -use std::sync::{LazyLock, OnceLock, PoisonError}; +use std::sync::LazyLock; const UNKNOWN_FUNCTION: &str = "B101"; -const SNOWFLAKE_FUNCTIONS: &str = include_str!("snowflake_functions.txt"); -const DUCKDB_CATALOG: &str = "duckdb"; -const PROBE_PREFIX: &str = "SELECT "; +const FUNCTION_SPELLINGS: &str = include_str!("function_spellings.txt"); +const SNOWFLAKE_DIALECT: &str = "snowflake"; +const DUCKDB_DIALECT: &str = "duckdb"; const TYPE_POSITION_KEYWORD: &str = "AS"; -const PROBE_ARITIES: usize = 9; -/// Keywords that may precede a parenthesis without being a function call. -const NON_CALL_KEYWORDS: &str = "ALL AND ANY AS ASOF AT BEFORE BETWEEN BY CASE CHANGES CONNECT CUBE \ - DEFINE DISTINCT DISTINCTROW ELSE END EXCEPT EXCLUDE EXISTS FILTER FROM HAVING ILIKE IN \ - INTERSECT INTERVAL IS JOIN LATERAL LIKE MATCH_CONDITION MATCH_RECOGNIZE MEASURES MINUS NOT ON \ - OR OVER PARTITION PATTERN PERCENT PIVOT PRIOR QUALIFY RANGE RENAME ROLLUP ROW ROWS SAMPLE \ - SELECT SETS SOME TABLE TABLESAMPLE THEN UNION UNPIVOT USING VALUES WHEN WHERE WITH WITHIN"; /// Words after which an expression, and so a function call, may start. const EXPRESSION_KEYWORDS: &str = "ALL AND ANY BETWEEN BY CASE DISTINCT ELSE ELSEIF ESCAPE HAVING \ @@ -37,10 +26,9 @@ const NOT_KEYWORD: &str = "NOT"; const MATERIALIZED_KEYWORD: &str = "MATERIALIZED"; const DISTINCT_KEYWORD: &str = "DISTINCT"; -type SpellingIndex = FxHashMap>; +/// Where one rejected spelling applies: the dialect, the argument count and the accepted spelling. +type SpellingRow = (&'static str, usize, &'static str); -static KEYWORDS: LazyLock> = - LazyLock::new(|| NON_CALL_KEYWORDS.split_whitespace().collect()); static EXPRESSION_STARTS: LazyLock> = LazyLock::new(|| EXPRESSION_KEYWORDS.split_whitespace().collect()); static CLAUSE_KEYWORDS: LazyLock> = @@ -49,17 +37,8 @@ static ROOT_FRAME: Frame = Frame { select_seen: false, clause: Clause::Other, }; -static SNOWFLAKE: LazyLock> = - LazyLock::new(|| accepted_names(SNOWFLAKE_FUNCTIONS)); -static DUCKDB: LazyLock> = LazyLock::new(|| { - let mut sink: CatalogNames = CatalogNames::default(); - polyglot_sql_function_catalogs::register_enabled_catalogs(&mut sink); - sink.names -}); -static SNOWFLAKE_INDEX: [OnceLock; PROBE_ARITIES] = - [const { OnceLock::new() }; PROBE_ARITIES]; -static DUCKDB_INDEX: [OnceLock; PROBE_ARITIES] = - [const { OnceLock::new() }; PROBE_ARITIES]; +static SPELLINGS: LazyLock>> = + LazyLock::new(|| spellings(FUNCTION_SPELLINGS)); /// One call site whose authored built-in spelling the dialect does not accept. #[derive(Debug, PartialEq, Eq)] @@ -95,21 +74,6 @@ struct Scan { path_colon: bool, } -/// How the parser represents one probe call. -#[derive(Clone, Debug, PartialEq, Eq, Hash)] -enum Signature { - /// A generic function node with this upper-case name. - Named(String), - /// A typed built-in node, identified by its expression variant. - Typed(Discriminant), -} - -/// Upper-case DuckDB names from the SQL library's introspected function catalogue. -#[derive(Default)] -struct CatalogNames { - names: FxHashSet, -} - /// Incremental byte-to-character position, so locating every call stays linear in the SQL. pub(crate) struct Cursor { byte: usize, @@ -118,29 +82,6 @@ pub(crate) struct Cursor { pub(crate) column: usize, } -impl CatalogSink for CatalogNames { - fn set_dialect_name_case(&mut self, _dialect: &'static str, _name_case: FunctionNameCase) {} - - fn set_function_name_case( - &mut self, - _dialect: &'static str, - _function_name: &str, - _name_case: FunctionNameCase, - ) { - } - - fn register( - &mut self, - dialect: &'static str, - function_name: &str, - _signatures: Vec, - ) { - if dialect == DUCKDB_CATALOG && is_identifier(function_name) { - self.names.insert(function_name.to_ascii_uppercase()); - } - } -} - impl Cursor { pub(crate) fn new() -> Self { Self { @@ -165,52 +106,50 @@ impl Cursor { } } -impl FunctionProbes { - /// Return the accepted spelling for an unaccepted call-site name the parser treats as built-in. - fn suggestion(&self, dialect: DialectType, upper: &str, arity: usize) -> Option { - let key: ProbeKey = (dialect, upper.to_owned(), arity.min(PROBE_ARITIES - 1)); - if let Some(cached) = self - .suggestions - .lock() - .unwrap_or_else(PoisonError::into_inner) - .get(&key) +/// Read `dialect SPELLING arguments ACCEPTED` rows; `#` starts a comment. +fn spellings(source: &'static str) -> FxHashMap<&'static str, Vec> { + let mut rows: FxHashMap<&'static str, Vec> = FxHashMap::default(); + for line in source.lines().map(str::trim) { + let mut fields = line.split_whitespace(); + if let (Some(dialect), Some(spelling), Some(arity), Some(accepted)) = + (fields.next(), fields.next(), fields.next(), fields.next()) + && !dialect.starts_with('#') + && let Ok(arity) = arity.parse::() { - return cached.clone(); + rows.entry(spelling) + .or_default() + .push((dialect, arity, accepted)); } - let found: Option = closest_spelling(dialect, upper, key.2); - self.suggestions - .lock() - .unwrap_or_else(PoisonError::into_inner) - .insert(key, found.clone()); - found } + rows } -fn accepted_names(source: &str) -> FxHashSet { - source - .lines() - .map(str::trim) - .filter(|line| !line.is_empty() && !line.starts_with('#')) - .map(str::to_ascii_uppercase) - .collect() +/// Return the accepted spelling for `upper` called with `arity` arguments in `dialect`. +fn accepted_spelling(dialect: &str, upper: &str, arity: usize) -> Option<&'static str> { + SPELLINGS + .get(upper)? + .iter() + .find_map(|(row_dialect, row_arity, accepted)| { + (*row_dialect == dialect && *row_arity == arity).then_some(*accepted) + }) } -fn accepted(dialect: DialectType) -> Option<&'static FxHashSet> { +/// The analysis dialect name the spelling table is keyed on, for the legacy validation path. +fn dialect_name(dialect: DialectType) -> Option<&'static str> { match dialect { - DialectType::Snowflake => Some(&SNOWFLAKE), - DialectType::DuckDB => Some(&DUCKDB), + DialectType::Snowflake => Some(SNOWFLAKE_DIALECT), + DialectType::DuckDB => Some(DUCKDB_DIALECT), _ => None, } } /// Return located B101 diagnostics, with character spans, for unsupported built-in spellings. -pub(crate) fn unsupported_function_errors( - sql: &str, - dialect: DialectType, - probes: &FunctionProbes, -) -> Vec { +pub(crate) fn unsupported_function_errors(sql: &str, dialect: DialectType) -> Vec { let mut cursor: Cursor = Cursor::new(); - unsupported_calls(sql, dialect, probes) + let Some(name) = dialect_name(dialect) else { + return Vec::new(); + }; + unsupported_calls(sql, name) .into_iter() .map(|call| { let message: String = format!( @@ -227,15 +166,8 @@ pub(crate) fn unsupported_function_errors( .collect() } -/// Return call sites in `sql` whose built-in spelling `dialect` does not accept. -pub(crate) fn unsupported_calls( - sql: &str, - dialect: DialectType, - probes: &FunctionProbes, -) -> Vec { - let Some(names) = accepted(dialect) else { - return Vec::new(); - }; +/// Return call sites in `sql` whose built-in spelling the analysis `dialect` does not accept. +pub(crate) fn unsupported_calls(sql: &str, dialect: &str) -> Vec { let bytes: &[u8] = sql.as_bytes(); let mut calls: Vec = Vec::new(); let mut scan: Scan = Scan::new(); @@ -277,18 +209,15 @@ pub(crate) fn unsupported_calls( let open: usize = skip_trivia(bytes, index); if bytes.get(open) == Some(&b'(') && scan.call_position() - && !name.starts_with("__") - && !names.contains(&upper) - && !KEYWORDS.contains(upper.as_str()) && let Some((arity, close)) = arguments(bytes, open) && !opens_cte_body(bytes, close + 1) - && let Some(suggestion) = probes.suggestion(dialect, &upper, arity) + && let Some(suggestion) = accepted_spelling(dialect, &upper, arity) { calls.push(UnsupportedCall { name: name.to_owned(), start, end: index, - suggestion, + suggestion: suggestion.to_owned(), }); } scan.word(upper); @@ -416,13 +345,6 @@ pub(crate) fn is_word_byte(byte: u8) -> bool { byte.is_ascii_alphanumeric() || byte == b'_' || byte == b'$' } -fn is_identifier(name: &str) -> bool { - name.bytes() - .next() - .is_some_and(|byte| byte.is_ascii_alphabetic() || byte == b'_') - && name.bytes().all(is_word_byte) -} - /// Return the top-level argument count and closing index of the call opening at `open`. fn arguments(sql: &[u8], open: usize) -> Option<(usize, usize)> { let mut depth: usize = 0; @@ -463,76 +385,3 @@ fn arguments(sql: &[u8], open: usize) -> Option<(usize, usize)> { } None } - -/// Return the closest accepted spelling the parser maps to the same built-in node as `upper`. -fn closest_spelling(dialect: DialectType, upper: &str, arity: usize) -> Option { - let signature: Signature = parsed_signature(dialect, upper, arity)?; - if signature == Signature::Named(upper.to_owned()) { - return None; - } - spelling_index(dialect, arity)? - .get(&signature)? - .iter() - .min_by_key(|accepted| (edit_distance(upper, accepted), (*accepted).clone())) - .cloned() -} - -fn spelling_index(dialect: DialectType, arity: usize) -> Option<&'static SpellingIndex> { - let (names, indexes) = match dialect { - DialectType::Snowflake => (&*SNOWFLAKE, &SNOWFLAKE_INDEX), - DialectType::DuckDB => (&*DUCKDB, &DUCKDB_INDEX), - _ => return None, - }; - Some(indexes.get(arity)?.get_or_init(|| { - let mut index: SpellingIndex = FxHashMap::default(); - for name in names { - if let Some(signature) = parsed_signature(dialect, name, arity) { - index.entry(signature).or_default().push(name.clone()); - } - } - index - })) -} - -fn parsed_signature(dialect: DialectType, upper: &str, arity: usize) -> Option { - let arguments: Vec = (1..=arity).map(|index| format!("a{index}")).collect(); - let sql: String = format!("{PROBE_PREFIX}{upper}({})", arguments.join(", ")); - let Ok(statements) = Dialect::get(dialect).parse(&sql) else { - return None; - }; - let [Expression::Select(select)] = statements.as_slice() else { - return None; - }; - let [expression] = select.expressions.as_slice() else { - return None; - }; - match expression { - Expression::Function(function) => { - Some(Signature::Named(function.name.to_ascii_uppercase())) - } - Expression::AggregateFunction(function) => { - Some(Signature::Named(function.name.to_ascii_uppercase())) - } - Expression::Column(_) - | Expression::Identifier(_) - | Expression::Tuple(_) - | Expression::Paren(_) => None, - other => Some(Signature::Typed(std::mem::discriminant(other))), - } -} - -fn edit_distance(left: &str, right: &str) -> usize { - let right: Vec = right.chars().collect(); - let mut previous: Vec = (0..=right.len()).collect(); - for (row, left_char) in left.chars().enumerate() { - let mut current: Vec = vec![row + 1; right.len() + 1]; - for (column, right_char) in right.iter().enumerate() { - let substitution: usize = previous[column] + usize::from(left_char != *right_char); - current[column + 1] = substitution - .min(previous[column + 1] + 1) - .min(current[column] + 1); - } - previous = current; - } - previous[right.len()] -} diff --git a/crates/sqlbuild-analysis/src/semantic_validation/_helpers/function_spellings.txt b/crates/sqlbuild-analysis/src/semantic_validation/_helpers/function_spellings.txt new file mode 100644 index 0000000000..06b81043cd --- /dev/null +++ b/crates/sqlbuild-analysis/src/semantic_validation/_helpers/function_spellings.txt @@ -0,0 +1,48 @@ +# Built-in spellings SQLBuild's compile-time name check (B101) rejects, with the spelling the +# dialect accepts for the same function. A call is rejected only when its spelling and argument +# count appear here, so user-defined and unknown functions are never rejected. +# +# Columns: analysis dialect, upper-case spelling, argument count, accepted spelling. +# Every Snowflake spelling is absent from Snowflake's function reference; every DuckDB spelling +# is unknown to DuckDB 2.0.0 with its core extensions. Add a row only with that evidence. +snowflake CEILING 1 CEIL +snowflake CEILING 2 CEIL +snowflake CHARACTER_LENGTH 1 LENGTH +snowflake CHAR_LENGTH 1 LENGTH +snowflake COUNTIF 1 COUNT_IF +snowflake DATEPART 2 DATE_PART +snowflake DAY_OF_MONTH 1 DAYOFMONTH +snowflake DAY_OF_WEEK 1 DAYOFWEEK +snowflake DAY_OF_YEAR 1 DAYOFYEAR +snowflake ENDS_WITH 2 ENDSWITH +snowflake IF 2 IFF +snowflake IF 3 IFF +snowflake IIF 2 IFF +snowflake IIF 3 IFF +snowflake INSTR 2 POSITION +snowflake INSTR 3 POSITION +snowflake LCASE 1 LOWER +snowflake LOCATE 2 POSITION +snowflake LOCATE 3 POSITION +snowflake RAND 1 RANDOM +snowflake STARTS_WITH 2 STARTSWITH +snowflake STRPOS 2 POSITION +snowflake STRPOS 3 POSITION +snowflake TEXTLEN 1 LEN +snowflake TOTYPENAME 1 TYPEOF +snowflake UCASE 1 UPPER +snowflake WEEK_OF_YEAR 1 WEEKOFYEAR +duckdb DAY_OF_MONTH 1 DAYOFMONTH +duckdb DAY_OF_WEEK 1 DAYOFWEEK +duckdb DAY_OF_YEAR 1 DAYOFYEAR +duckdb ENDSWITH 2 ENDS_WITH +duckdb EVERY 1 BOOL_AND +duckdb LOCATE 2 INSTR +duckdb LOCATE 3 INSTR +duckdb LOGICAL_AND 1 BOOL_AND +duckdb LOGICAL_OR 1 BOOL_OR +duckdb RAND 1 RANDOM +duckdb STARTSWITH 2 STARTS_WITH +duckdb TEXTLEN 1 LEN +duckdb TOTYPENAME 1 TYPEOF +duckdb WEEK_OF_YEAR 1 WEEKOFYEAR diff --git a/crates/sqlbuild-analysis/src/semantic_validation/_helpers/snowflake_functions.txt b/crates/sqlbuild-analysis/src/semantic_validation/_helpers/snowflake_functions.txt deleted file mode 100644 index 6147565f61..0000000000 --- a/crates/sqlbuild-analysis/src/semantic_validation/_helpers/snowflake_functions.txt +++ /dev/null @@ -1,993 +0,0 @@ -# Snowflake built-in function spellings accepted by SQLBuild's compile-time name check (B101). -# -# Source: Snowflake's public "All functions (alphabetical)" reference, including every listed -# alias (for example SUBSTR and SUBSTRING, DATEDIFF, TIMEDIFF and TIMESTAMPDIFF, TRY_TO_DECIMAL, -# TRY_TO_NUMBER and TRY_TO_NUMERIC, STARTSWITH, ENDSWITH, CONTAINS, IFF), with wildcard families -# such as TO_TIMESTAMP_* and YEAR*/DAY*/WEEK* expanded, plus SYS_CONNECT_BY_PATH from the -# CONNECT BY reference. Names are case-insensitive; one name per line; `#` starts a comment. -# -# A spelling absent from this list is rejected only when the SQL parser maps it to the same -# built-in node as a listed spelling, so user-defined and unknown functions are never rejected. -# Add a missing Snowflake built-in here rather than suppressing the diagnostic. -ABS -ACCEPTED_VALUES -ACOS -ACOSH -ADD_MONTHS -AGENT_RUN -AGG -AI_AGG -AI_CLASSIFY -AI_COMPLETE -AI_COUNT_TOKENS -AI_EMBED -AI_EXTRACT -AI_FILTER -AI_MULTI_EMBED -AI_PARSE_DOCUMENT -AI_REDACT -AI_SENTIMENT -AI_SIMILARITY -AI_SUMMARIZE -AI_SUMMARIZE_AGG -AI_TRANSCRIBE -AI_TRANSLATE -ALERT_HISTORY -ALL_USER_NAMES -ANY_VALUE -APPLICATION_CALLBACK_HISTORY -APPLICATION_CONFIGURATION_VALUE_HISTORY -APPLICATION_JSON -APPLICATION_SPECIFICATION_STATUS_HISTORY -APPROXIMATE_JACCARD_INDEX -APPROXIMATE_SIMILARITY -APPROX_COUNT_DISTINCT -APPROX_PERCENTILE -APPROX_PERCENTILE_ACCUMULATE -APPROX_PERCENTILE_COMBINE -APPROX_PERCENTILE_ESTIMATE -APPROX_TOP_K -APPROX_TOP_K_ACCUMULATE -APPROX_TOP_K_COMBINE -APPROX_TOP_K_ESTIMATE -ARRAYS_OVERLAP -ARRAYS_TO_OBJECT -ARRAYS_ZIP -ARRAY_AGG -ARRAY_APPEND -ARRAY_CAT -ARRAY_COMPACT -ARRAY_CONSTRUCT -ARRAY_CONSTRUCT_COMPACT -ARRAY_CONSTRUCT_STRUCTURED -ARRAY_CONTAINS -ARRAY_DISTINCT -ARRAY_EXCEPT -ARRAY_FLATTEN -ARRAY_GENERATE_RANGE -ARRAY_INSERT -ARRAY_INTERSECTION -ARRAY_MAX -ARRAY_MIN -ARRAY_POSITION -ARRAY_PREPEND -ARRAY_REMOVE -ARRAY_REMOVE_AT -ARRAY_REVERSE -ARRAY_SIZE -ARRAY_SLICE -ARRAY_SORT -ARRAY_TO_STRING -ARRAY_UNION_AGG -ARRAY_UNIQUE_AGG -ASCII -ASIN -ASINH -AS_ARRAY -AS_BINARY -AS_BOOLEAN -AS_CHAR -AS_DATE -AS_DECIMAL -AS_DOUBLE -AS_INTEGER -AS_NUMBER -AS_OBJECT -AS_REAL -AS_TIME -AS_TIMESTAMP_LTZ -AS_TIMESTAMP_NTZ -AS_TIMESTAMP_TZ -AS_VARCHAR -ATAN -ATAN2 -ATANH -AUTOMATIC_CLUSTERING_HISTORY -AUTO_REFRESH_REGISTRATION_HISTORY -AVAILABLE_LISTINGS -AVAILABLE_LISTING_REFRESH_HISTORY -AVG -BASE64_DECODE_BINARY -BASE64_DECODE_STRING -BASE64_ENCODE -BETWEEN -BIND_VALUES -BITAND -BITAND_AGG -BITMAP_ABSOLUTE_POSITION -BITMAP_AND -BITMAP_AND_AGG -BITMAP_BIT_POSITION -BITMAP_BUCKET_NUMBER -BITMAP_CONSTRUCT_AGG -BITMAP_COUNT -BITMAP_OR -BITMAP_OR_AGG -BITMAP_TO_ARRAY -BITNOT -BITOR -BITOR_AGG -BITSHIFTLEFT -BITSHIFTRIGHT -BITXOR -BITXOR_AGG -BIT_LENGTH -BLANK_COUNT -BLANK_PERCENT -BOOLAND -BOOLAND_AGG -BOOLNOT -BOOLOR -BOOLOR_AGG -BOOLXOR -BOOLXOR_AGG -BUILD_SCOPED_FILE_URL -BUILD_STAGE_FILE_URL -CASE -CAST -CBRT -CEIL -CHAR -CHARINDEX -CHECK_JSON -CHECK_XML -CHR -CLASSIFY_TEXT -COALESCE -COLLATE -COLLATION -COMPLETE -COMPLETE_TASK_GRAPHS -COMPRESS -CONCAT -CONCAT_WS -CONDITIONAL_CHANGE_EVENT -CONDITIONAL_TRUE_EVENT -CONTAINS -CONVERT_TIMEZONE -COPY_HISTORY -CORR -CORTEX_SEARCH_BATCH -CORTEX_SEARCH_DATA_SCAN -CORTEX_SEARCH_REFRESH_HISTORY -COS -COSH -COT -COUNT -COUNT_IF -COUNT_TOKENS -COVAR_POP -COVAR_SAMP -CUME_DIST -CUMULATIVE_PRIVACY_LOSSES -CURRENT_ACCOUNT -CURRENT_ACCOUNT_NAME -CURRENT_AVAILABLE_ROLES -CURRENT_CLIENT -CURRENT_DATABASE -CURRENT_DATE -CURRENT_IP_ADDRESS -CURRENT_ORGANIZATION_NAME -CURRENT_ORGANIZATION_USER -CURRENT_REGION -CURRENT_ROLE -CURRENT_ROLE_TYPE -CURRENT_SCHEMA -CURRENT_SCHEMAS -CURRENT_SECONDARY_ROLES -CURRENT_SESSION -CURRENT_STATEMENT -CURRENT_TASK_GRAPHS -CURRENT_TIME -CURRENT_TIMESTAMP -CURRENT_TRANSACTION -CURRENT_USER -CURRENT_VERSION -CURRENT_WAREHOUSE -DATABASE_REFRESH_HISTORY -DATABASE_REFRESH_PROGRESS -DATABASE_REFRESH_PROGRESS_BY_JOB -DATABASE_REPLICATION_USAGE_HISTORY -DATABASE_STORAGE_USAGE_HISTORY -DATASKETCHES_HLL -DATASKETCHES_HLL_ACCUMULATE -DATASKETCHES_HLL_COMBINE -DATASKETCHES_HLL_ESTIMATE -DATA_AGENT_RUN -DATA_METRIC_FUNCTION_EXPECTATIONS -DATA_METRIC_FUNCTION_REFERENCES -DATA_METRIC_SCHEDULED_TIME -DATA_QUALITY_MONITORING_EXPECTATION_STATUS -DATA_QUALITY_MONITORING_RESULTS -DATA_TRANSFER_HISTORY -DATE -DATEADD -DATEDIFF -DATE_FROM_PARTS -DATE_PART -DATE_TRUNC -DAY -DAYNAME -DAYOFMONTH -DAYOFWEEK -DAYOFWEEKISO -DAYOFYEAR -DBT_PROJECT_EXECUTION_HISTORY -DECODE -DECOMPRESS_BINARY -DECOMPRESS_STRING -DECRYPT -DECRYPT_RAW -DEGREES -DENSE_RANK -DIV0 -DIV0NULL -DP_INTERVAL_HIGH -DP_INTERVAL_LOW -DUPLICATE_COUNT -DYNAMIC_TABLES -DYNAMIC_TABLE_GRAPH_HISTORY -DYNAMIC_TABLE_REFRESH_HISTORY -EDITDISTANCE -EMAIL_INTEGRATION_CONFIG -EMBED_TEXT_1024 -EMBED_TEXT_768 -ENCRYPT -ENCRYPT_RAW -ENDSWITH -ENTITY_SENTIMENT -EQUAL_NULL -ESTIMATE_REMAINING_DP_AGGREGATES -EXECUTE_AI_EVALUATION -EXP -EXPLAIN_JSON -EXTERNAL_FUNCTIONS_HISTORY -EXTERNAL_TABLE_FILES -EXTERNAL_TABLE_FILE_REGISTRATION_HISTORY -EXTRACT -EXTRACT_ANSWER -EXTRACT_SEMANTIC_CATEGORIES -EXTREME_OUTLIER_COUNT -EXTREME_OUTLIER_IQR_COUNT -EXTREME_OUTLIER_IQR_PERCENT -EXTREME_OUTLIER_PERCENT -EXTREME_OUTLIER_ZSCORE_COUNT -EXTREME_OUTLIER_ZSCORE_PERCENT -FACTORIAL -FILTER -FINETUNE -FIRST_VALUE -FLATTEN -FLOOR -FL_GET_CONTENT_TYPE -FL_GET_ETAG -FL_GET_FILE_TYPE -FL_GET_LAST_MODIFIED -FL_GET_RELATIVE_PATH -FL_GET_SCOPED_FILE_URL -FL_GET_SIZE -FL_GET_STAGE -FL_GET_STAGE_FILE_URL -FL_IS_AUDIO -FL_IS_COMPRESSED -FL_IS_DOCUMENT -FL_IS_IMAGE -FL_IS_VIDEO -FRESHNESS -GENERATE_COLUMN_DESCRIPTION -GENERATE_POSTGRES_ACCESS_TOKEN_FOR_USER -GENERATOR -GET -GETBIT -GETDATE -GETVARIABLE -GET_ABSOLUTE_PATH -GET_AI_EVALUATION_DATA -GET_AI_OBSERVABILITY_LOGS -GET_AI_RECORD_TRACE -GET_ANACONDA_PACKAGES_REPODATA -GET_CONDITION_QUERY_UUID -GET_CONFIGURATION_VALUE -GET_CONTACTS -GET_DDL -GET_IGNORE_CASE -GET_JOB_HISTORY -GET_LINEAGE -GET_OBJECT_REFERENCES -GET_PATH -GET_PRESIGNED_URL -GET_PYTHON_PROFILER_OUTPUT -GET_QUERY_OPERATOR_STATS -GET_RELATIVE_PATH -GET_STAGE_LOCATION -GREATEST -GREATEST_IGNORE_NULLS -GROUPING -GROUPING_ID -H3_CELL_TO_BOUNDARY -H3_CELL_TO_CHILDREN -H3_CELL_TO_CHILDREN_STRING -H3_CELL_TO_PARENT -H3_CELL_TO_POINT -H3_COMPACT_CELLS -H3_COMPACT_CELLS_STRINGS -H3_COVERAGE -H3_COVERAGE_STRINGS -H3_GET_RESOLUTION -H3_GRID_DISK -H3_GRID_DISTANCE -H3_GRID_PATH -H3_INT_TO_STRING -H3_IS_PENTAGON -H3_IS_VALID_CELL -H3_LATLNG_TO_CELL -H3_LATLNG_TO_CELL_STRING -H3_POINT_TO_CELL -H3_POINT_TO_CELL_STRING -H3_POLYGON_TO_CELLS -H3_POLYGON_TO_CELLS_STRINGS -H3_STRING_TO_INT -H3_TRY_COVERAGE -H3_TRY_COVERAGE_STRINGS -H3_TRY_GRID_DISTANCE -H3_TRY_GRID_PATH -H3_TRY_POLYGON_TO_CELLS -H3_TRY_POLYGON_TO_CELLS_STRINGS -H3_UNCOMPACT_CELLS -H3_UNCOMPACT_CELLS_STRINGS -HASH -HASH_AGG -HAVERSINE -HEX_DECODE_BINARY -HEX_DECODE_STRING -HEX_ENCODE -HLL -HLL_ACCUMULATE -HLL_COMBINE -HLL_ESTIMATE -HLL_EXPORT -HLL_IMPORT -HOUR -ICEBERG_TABLE_FILES -ICEBERG_TABLE_SNAPSHOT_REFRESH_HISTORY -IFF -IFNULL -ILIKE -IN -INFER_SCHEMA -INITCAP -INSERT -INTEGRATION -INTERPOLATE_BFILL -INTERPOLATE_FFILL -INTERPOLATE_LINEAR -INVOKER_ROLE -INVOKER_SHARE -IS_APPLICATION_ROLE_ACTIVATED -IS_APPLICATION_ROLE_IN_SESSION -IS_ARRAY -IS_BINARY -IS_BOOLEAN -IS_CHAR -IS_CONFIGURATION_SET -IS_DATABASE_ROLE_ACTIVATED -IS_DATABASE_ROLE_IN_SESSION -IS_DATE -IS_DATE_VALUE -IS_DECIMAL -IS_DOUBLE -IS_GRANTED_TO_INVOKER_ROLE -IS_GROUP_ACTIVATED -IS_GROUP_IMPORTED -IS_INSTANCE_ROLE_IN_SESSION -IS_INTEGER -IS_NULL_VALUE -IS_OBJECT -IS_ORGANIZATION_USER -IS_ORGANIZATION_USER_GROUP -IS_ORGANIZATION_USER_GROUP_IN_SESSION -IS_REAL -IS_ROLE_ACTIVATED -IS_ROLE_IN_SESSION -IS_TIME -IS_TIMESTAMP_LTZ -IS_TIMESTAMP_NTZ -IS_TIMESTAMP_TZ -IS_USER_IMPORTED -IS_VARCHAR -JAROWINKLER_SIMILARITY -JSON_EXTRACT_PATH_TEXT -KURTOSIS -LAG -LAST_DAY -LAST_QUERY_ID -LAST_SUCCESSFUL_SCHEDULED_TIME -LAST_TRANSACTION -LAST_VALUE -LEAD -LEAST -LEAST_IGNORE_NULLS -LEFT -LEN -LENGTH -LIKE -LISTAGG -LISTING_REFRESH_HISTORY -LN -LOCALTIME -LOCALTIMESTAMP -LOG -LOGIN_HISTORY -LOGIN_HISTORY_BY_USER -LOWER -LPAD -LTRIM -MAP_CAT -MAP_CONSTRUCT -MAP_CONTAINS_KEY -MAP_DELETE -MAP_ENTRIES -MAP_INSERT -MAP_KEYS -MAP_PICK -MAP_SIZE -MATERIALIZED_VIEW_REFRESH_HISTORY -MAX -MAX_BY -MD5 -MD5_BINARY -MD5_HEX -MD5_NUMBER_LOWER64 -MD5_NUMBER_UPPER64 -MEDIAN -MIN -MINHASH -MINHASH_COMBINE -MINUTE -MIN_BY -MOD -MODE -MODEL_MONITOR_DRIFT_METRIC -MODEL_MONITOR_PERFORMANCE_METRIC -MODEL_MONITOR_STAT_METRIC -MONTH -MONTHNAME -MONTHS_BETWEEN -NETWORK_RULE_REFERENCES -NEXT_DAY -NORMAL -NOTIFICATION_HISTORY -NTH_VALUE -NTILE -NULLIF -NULLIFZERO -NULL_COUNT -NULL_PERCENT -NVL -NVL2 -OBJECT_AGG -OBJECT_CONSTRUCT -OBJECT_CONSTRUCT_KEEP_NULL -OBJECT_CONSTRUCT_KEEP_NULL_STRUCTURED -OBJECT_DELETE -OBJECT_INSERT -OBJECT_KEYS -OBJECT_PICK -OCTET_LENGTH -ONLINE_FEATURE_TABLE_REFRESH_HISTORY -OUTLIER_COUNT -OUTLIER_IQR_COUNT -OUTLIER_IQR_PERCENT -OUTLIER_PERCENT -OUTLIER_ZSCORE_COUNT -OUTLIER_ZSCORE_PERCENT -PARSE_DOCUMENT -PARSE_IP -PARSE_JSON -PARSE_URL -PARSE_XML -PERCENTILE_CONT -PERCENTILE_DISC -PERCENT_RANK -PI -PIPE_USAGE_HISTORY -POLICY_CONTEXT -POLICY_REFERENCES -POSITION -POW -POWER -PREVIOUS_DAY -PROMPT -QUARTER -QUERY_ACCELERATION_HISTORY -QUERY_HISTORY -RADIANS -RANDOM -RANDSTR -RANK -RATIO_TO_REPORT -REDUCE -REGEXP -REGEXP_COUNT -REGEXP_INSTR -REGEXP_LIKE -REGEXP_REPLACE -REGEXP_SUBSTR -REGEXP_SUBSTR_ALL -REGR_AVGX -REGR_AVGY -REGR_COUNT -REGR_INTERCEPT -REGR_R2 -REGR_SLOPE -REGR_SXX -REGR_SXY -REGR_SYY -REGR_VALX -REGR_VALY -REPEAT -REPLACE -REPLICATION_GROUP_DANGLING_REFERENCES -REPLICATION_GROUP_REFRESH_HISTORY -REPLICATION_GROUP_REFRESH_HISTORY_ALL -REPLICATION_GROUP_REFRESH_PROGRESS -REPLICATION_GROUP_REFRESH_PROGRESS_ALL -REPLICATION_GROUP_REFRESH_PROGRESS_BY_JOB -REPLICATION_GROUP_USAGE_HISTORY -REPLICATION_USAGE_HISTORY -REST_EVENT_HISTORY -RESULT_SCAN -REVERSE -RIGHT -RLIKE -ROUND -ROW_COUNT -ROW_NUMBER -RPAD -RTRIM -RTRIMMED_LENGTH -SANITIZE_WEBHOOK_CONTENT -SCHEDULED_TIME -SEARCH -SEARCH_IP -SEARCH_OPTIMIZATION_HISTORY -SEARCH_PREVIEW -SECOND -SENTIMENT -SEQ1 -SEQ2 -SEQ4 -SEQ8 -SERVERLESS_ALERT_HISTORY -SERVERLESS_TASK_HISTORY -SET_SYS_CONTEXT -SHA1 -SHA1_BINARY -SHA1_HEX -SHA2 -SHA2_BINARY -SHA2_HEX -SHOW_PYTHON_PACKAGES_DEPENDENCIES -SIGN -SIN -SINH -SKEW -SOUNDEX -SOUNDEX_P123 -SPACE -SPLIT -SPLIT_PART -SPLIT_TEXT_MARKDOWN_HEADER -SPLIT_TEXT_RECURSIVE_CHARACTER -SPLIT_TO_TABLE -SQRT -SQUARE -STAGE_DIRECTORY_FILE_REGISTRATION_HISTORY -STAGE_STORAGE_USAGE_HISTORY -STARTSWITH -STDDEV -STDDEV_POP -STDDEV_SAMP -STORAGE_LIFECYCLE_POLICY_HISTORY -STRIP_NULL_VALUE -STRTOK -STRTOK_SPLIT_TO_TABLE -STRTOK_TO_ARRAY -ST_AREA -ST_ASBINARY -ST_ASEWKB -ST_ASEWKT -ST_ASGEOJSON -ST_ASTEXT -ST_ASWKB -ST_ASWKT -ST_AZIMUTH -ST_BUFFER -ST_CENTROID -ST_COLLECT -ST_CONTAINS -ST_COVEREDBY -ST_COVERS -ST_DIFFERENCE -ST_DIMENSION -ST_DISJOINT -ST_DISTANCE -ST_DWITHIN -ST_ENDPOINT -ST_ENVELOPE -ST_GEOGFROMGEOHASH -ST_GEOGPOINTFROMGEOHASH -ST_GEOGRAPHYFROMWKB -ST_GEOGRAPHYFROMWKT -ST_GEOHASH -ST_GEOMETRYFROMWKB -ST_GEOMETRYFROMWKT -ST_GEOMFROMGEOHASH -ST_GEOMPOINT -ST_GEOMPOINTFROMGEOHASH -ST_HAUSDORFFDISTANCE -ST_INTERPOLATE -ST_INTERSECTION -ST_INTERSECTION_AGG -ST_INTERSECTS -ST_ISVALID -ST_LENGTH -ST_MAKEGEOMPOINT -ST_MAKELINE -ST_MAKEPOINT -ST_MAKEPOLYGON -ST_MAKEPOLYGONORIENTED -ST_NPOINTS -ST_NUMPOINTS -ST_PERIMETER -ST_POINT -ST_POINTN -ST_POLYGON -ST_SETSRID -ST_SIMPLIFY -ST_SRID -ST_STARTPOINT -ST_SYMDIFFERENCE -ST_TRANSFORM -ST_UNION -ST_UNION_AGG -ST_WITHIN -ST_X -ST_XMAX -ST_XMIN -ST_Y -ST_YMAX -ST_YMIN -SUBSTR -SUBSTRING -SUM -SYSDATE -SYSTEM$ABORT_SESSION -SYSTEM$ABORT_TRANSACTION -SYSTEM$ACTIVATE_CMK_INFO -SYSTEM$ACTIVATE_CMK_INFO_POSTGRES -SYSTEM$ADD_EVENT -SYSTEM$ADD_REFERENCE -SYSTEM$ALLOWLIST -SYSTEM$ALLOWLIST_PRIVATELINK -SYSTEM$APPLICATION_GET_LOG_LEVEL -SYSTEM$APPLICATION_GET_METRIC_LEVEL -SYSTEM$APPLICATION_GET_TRACE_LEVEL -SYSTEM$APP_COMPATIBILITY_CHECK -SYSTEM$AUTHORIZE_PRIVATELINK -SYSTEM$AUTHORIZE_STAGE_PRIVATELINK_ACCESS -SYSTEM$AUTO_REFRESH_STATUS -SYSTEM$BEGIN_DEBUG_APPLICATION -SYSTEM$BEHAVIOR_CHANGE_BUNDLE_STATUS -SYSTEM$BLOCK_INTERNAL_STAGES_PUBLIC_ACCESS -SYSTEM$BLOCK_INTERNAL_STAGES_PUBLIC_ACCESS_WITH_EXCEPTION -SYSTEM$CANCEL_ALL_QUERIES -SYSTEM$CANCEL_QUERY -SYSTEM$CATALOG_LINK_STATUS -SYSTEM$CKE_HASH_FUNCTION -SYSTEM$CLEANUP_DATABASE_ROLE_GRANTS -SYSTEM$CLIENT_VERSION_INFO -SYSTEM$CLIENT_VULNERABILITY_INFO -SYSTEM$CLUSTERING_DEPTH -SYSTEM$CLUSTERING_INFORMATION -SYSTEM$COMMIT_MOVE_ORGANIZATION_ACCOUNT -SYSTEM$CONVERT_PIPES_SQS_TO_SNS -SYSTEM$CREATE_BILLING_EVENT -SYSTEM$CREATE_BILLING_EVENTS -SYSTEM$CURRENT_USER_TASK_NAME -SYSTEM$DATABASE_REFRESH_PROGRESS -SYSTEM$DATA_METRIC_SCAN -SYSTEM$DEACTIVATE_CMK_INFO -SYSTEM$DECODE_PAT -SYSTEM$DEPROVISION_PRIVATELINK_ENDPOINT -SYSTEM$DEPROVISION_PRIVATELINK_ENDPOINT_TSS -SYSTEM$DEREGISTER_CMK_INFO -SYSTEM$DEREGISTER_CMK_INFO_POSTGRES -SYSTEM$DESC_ICEBERG_ACCESS_IDENTITY -SYSTEM$DISABLE_BEHAVIOR_CHANGE_BUNDLE -SYSTEM$DISABLE_DATABASE_REPLICATION -SYSTEM$DISABLE_GLOBAL_DATA_SHARING_FOR_ACCOUNT -SYSTEM$DISABLE_PREVIEW_ACCESS -SYSTEM$DISABLE_PRIVATELINK_ACCESS_ONLY -SYSTEM$ENABLE_BEHAVIOR_CHANGE_BUNDLE -SYSTEM$ENABLE_GLOBAL_DATA_SHARING_FOR_ACCOUNT -SYSTEM$ENABLE_PREVIEW_ACCESS -SYSTEM$ENCODE_CKE_PRIMARY_KEY -SYSTEM$END_DEBUG_APPLICATION -SYSTEM$ENFORCE_PRIVATELINK_ACCESS_ONLY -SYSTEM$ESTIMATE_AUTOMATIC_CLUSTERING_COSTS -SYSTEM$ESTIMATE_QUERY_ACCELERATION -SYSTEM$ESTIMATE_SEARCH_OPTIMIZATION_COSTS -SYSTEM$EVALUATE_DATA_QUALITY_EXPECTATIONS -SYSTEM$EVALUATE_DATA_QUALITY_EXPECTATIONS_PERSIST_RESULT -SYSTEM$EXPLAIN_JSON_TO_TEXT -SYSTEM$EXPLAIN_PLAN_JSON -SYSTEM$EXPORT_TDS_FROM_SEMANTIC_VIEW -SYSTEM$EXTERNAL_TABLE_PIPE_STATUS -SYSTEM$FINISH_OAUTH_FLOW -SYSTEM$GENERATE_SAML_CSR -SYSTEM$GENERATE_SCIM_ACCESS_TOKEN -SYSTEM$GET_ALL_DEFAULT_COLUMNS_OVERRIDES -SYSTEM$GET_ALL_REFERENCES -SYSTEM$GET_AWS_SNS_IAM_POLICY -SYSTEM$GET_CATALOG_LINKED_DATABASE_CONFIG -SYSTEM$GET_CLASSIFICATION_RESULT -SYSTEM$GET_CMK_AKV_CONSENT_URL -SYSTEM$GET_CMK_CONFIG -SYSTEM$GET_CMK_CONFIG_POSTGRES -SYSTEM$GET_CMK_INFO -SYSTEM$GET_CMK_INFO_POSTGRES -SYSTEM$GET_CMK_KMS_KEY_POLICY -SYSTEM$GET_COMPUTE_POOL_PENDING_MAINTENANCE -SYSTEM$GET_DBT_LOG -SYSTEM$GET_DEBUG_STATUS -SYSTEM$GET_DEFAULT_COLUMNS_OVERRIDE_FOR_SHOW_COMMAND -SYSTEM$GET_DEFAULT_COLUMNS_OVERRIDE_FOR_SYSTEM_OBJECT -SYSTEM$GET_DIRECTORY_TABLE_STATUS -SYSTEM$GET_GCP_KMS_CMK_GRANT_ACCESS_CMD -SYSTEM$GET_HASH_FOR_APPLICATION -SYSTEM$GET_ICEBERG_TABLE_INFORMATION -SYSTEM$GET_INSTANCE_FAMILY_PLACEMENT_GROUPS -SYSTEM$GET_LOGIN_FAILURE_DETAILS -SYSTEM$GET_PREDECESSOR_RETURN_VALUE -SYSTEM$GET_PREVIEW_ACCESS_STATUS -SYSTEM$GET_PRIVATELINK -SYSTEM$GET_PRIVATELINK_AUTHORIZED_ENDPOINTS -SYSTEM$GET_PRIVATELINK_CONFIG -SYSTEM$GET_PRIVATELINK_ENDPOINTS_INFO -SYSTEM$GET_PRIVATELINK_ENDPOINT_REGISTRATIONS -SYSTEM$GET_PURCHASE_ATTRIBUTES -SYSTEM$GET_REFERENCED_OBJECT_ID_HASH -SYSTEM$GET_RESULTSET_STATUS -SYSTEM$GET_SERVICE_DNS_DOMAIN -SYSTEM$GET_SERVICE_LOGS -SYSTEM$GET_SNOWFLAKE_EGRESS_IP_RANGES -SYSTEM$GET_SNOWFLAKE_PLATFORM_INFO -SYSTEM$GET_STAGE_PRIVATELINK_AUTHORIZED_ENDPOINTS -SYSTEM$GET_TAG -SYSTEM$GET_TAG_ALLOWED_VALUES -SYSTEM$GET_TAG_ON_CURRENT_COLUMN -SYSTEM$GET_TAG_ON_CURRENT_TABLE -SYSTEM$GET_TASK_GRAPH_CONFIG -SYSTEM$GLOBAL_ACCOUNT_SET_PARAMETER -SYSTEM$HOLD_PRIVILEGE_ON_ACCOUNT -SYSTEM$INITIATE_MOVE_ORGANIZATION_ACCOUNT -SYSTEM$INTERNAL_STAGES_PUBLIC_ACCESS_STATUS -SYSTEM$IS_APPLICATION_ALL_MANDATORY_TELEMETRY_EVENT_DEFINITIONS_ENABLED -SYSTEM$IS_APPLICATION_AUTHORIZED_FOR_TELEMETRY_EVENT_SHARING -SYSTEM$IS_APPLICATION_INSTALLED_FROM_SAME_ACCOUNT -SYSTEM$IS_APPLICATION_SHARING_EVENTS_WITH_PROVIDER -SYSTEM$IS_GLOBAL_DATA_SHARING_ENABLED_FOR_ACCOUNT -SYSTEM$IS_LISTING_PURCHASED -SYSTEM$IS_LISTING_TRIAL -SYSTEM$LAST_CHANGE_COMMIT_TIME -SYSTEM$LINK_ACCOUNT_OBJECTS_BY_NAME -SYSTEM$LINK_ORGANIZATION_USER -SYSTEM$LINK_ORGANIZATION_USER_GROUP -SYSTEM$LIST_APPLICATION_RESTRICTED_FEATURES -SYSTEM$LIST_ICEBERG_TABLES_FROM_CATALOG -SYSTEM$LIST_NAMESPACES_FROM_CATALOG -SYSTEM$LOCATE_DBT_ARCHIVE -SYSTEM$LOCATE_DBT_ARTIFACTS -SYSTEM$LOG -SYSTEM$MIGRATE_SAML_IDP_REGISTRATION -SYSTEM$OPT_IN_INTERNAL_STAGE_NETWORK_LOGS -SYSTEM$OPT_OUT_INTERNAL_STAGE_NETWORK_LOGS -SYSTEM$OPT_OUT_MALICIOUS_IP_PROTECTION_BY_CATEGORY -SYSTEM$PIPE_FORCE_RESUME -SYSTEM$PIPE_REBINDING_WITH_NOTIFICATION_CHANNEL -SYSTEM$PIPE_STATUS -SYSTEM$PROVISION_PRIVATELINK_ENDPOINT -SYSTEM$PROVISION_PRIVATELINK_ENDPOINT_TSS -SYSTEM$QUERY_REFERENCE -SYSTEM$READ_OSI_YAML_FROM_SEMANTIC_VIEW -SYSTEM$READ_YAML_FROM_SEMANTIC_VIEW -SYSTEM$REFERENCE -SYSTEM$REGISTER_CMK_INFO -SYSTEM$REGISTER_CMK_INFO_POSTGRES -SYSTEM$REGISTER_PRIVATELINK_ENDPOINT -SYSTEM$REMOVE_ALL_REFERENCES -SYSTEM$REMOVE_REFERENCE -SYSTEM$REPORT_HEALTH_STATUS -SYSTEM$RESOLVE_PYTHON_PACKAGES -SYSTEM$RESTORE_PRIVATELINK_ENDPOINT -SYSTEM$RESTORE_PRIVATELINK_ENDPOINT_TSS -SYSTEM$REVOKE_PRIVATELINK -SYSTEM$REVOKE_STAGE_PRIVATELINK_ACCESS -SYSTEM$SCHEDULE_ASYNC_REPLICATION_GROUP_REFRESH -SYSTEM$SEND_NOTIFICATIONS_TO_CATALOG -SYSTEM$SET_APPLICATION_RESTRICTED_FEATURE_ACCESS -SYSTEM$SET_CATALOG_INTEGRATION -SYSTEM$SET_DEFAULT_COLUMNS_OVERRIDE_FOR_SHOW_COMMAND -SYSTEM$SET_DEFAULT_COLUMNS_OVERRIDE_FOR_SYSTEM_OBJECT -SYSTEM$SET_EVENT_SHARING_ACCOUNT_FOR_REGION -SYSTEM$SET_PRIVATELINK_ENDPOINT_HOSTNAME -SYSTEM$SET_REFERENCE -SYSTEM$SET_RETURN_VALUE -SYSTEM$SET_ROW_TIMESTAMP_ON_ALL_SUPPORTED_TABLES -SYSTEM$SET_SPAN_ATTRIBUTES -SYSTEM$SHOW_ACTIVE_BEHAVIOR_CHANGE_BUNDLES -SYSTEM$SHOW_BUDGETS_FOR_RESOURCE -SYSTEM$SHOW_BUDGETS_IN_ACCOUNT -SYSTEM$SHOW_EVENT_SHARING_ACCOUNTS -SYSTEM$SHOW_MOVE_ORGANIZATION_ACCOUNT_STATUS -SYSTEM$SHOW_OAUTH_CLIENT_SECRETS -SYSTEM$SHOW_SENSITIVE_DATA_MONITORED_ENTITIES -SYSTEM$SNOWPIPE_STREAMING_UPDATE_CHANNEL_OFFSET_TOKEN -SYSTEM$START_OAUTH_FLOW -SYSTEM$START_USER_EMAIL_VERIFICATION -SYSTEM$STREAM_BACKLOG -SYSTEM$STREAM_GET_TABLE_TIMESTAMP -SYSTEM$STREAM_HAS_DATA -SYSTEM$SUPPORTED_DBT_VERSIONS -SYSTEM$TASK_DEPENDENTS_ENABLE -SYSTEM$TASK_RUNTIME_INFO -SYSTEM$TRIGGER_LISTING_REFRESH -SYSTEM$TYPEOF -SYSTEM$UNBLOCK_INTERNAL_STAGES_PUBLIC_ACCESS -SYSTEM$UNLINK_ORGANIZATION_USER -SYSTEM$UNLINK_ORGANIZATION_USER_GROUP -SYSTEM$UNREGISTER_PRIVATELINK_ENDPOINT -SYSTEM$UNSET_DEFAULT_COLUMNS_OVERRIDE_FOR_SHOW_COMMAND -SYSTEM$UNSET_DEFAULT_COLUMNS_OVERRIDE_FOR_SYSTEM_OBJECT -SYSTEM$UNSET_EVENT_SHARING_ACCOUNT_FOR_REGION -SYSTEM$USER_TASK_CANCEL_ONGOING_EXECUTIONS -SYSTEM$VALIDATE_STORAGE_INTEGRATION -SYSTEM$VERIFY_CATALOG_INTEGRATION -SYSTEM$VERIFY_CMK_INFO -SYSTEM$VERIFY_CMK_INFO_POSTGRES -SYSTEM$VERIFY_EXTERNAL_OAUTH_TOKEN -SYSTEM$VERIFY_EXTERNAL_VOLUME -SYSTEM$WAIT -SYSTEM$WAIT_FOR_SERVICES -SYSTIMESTAMP -SYS_CONNECT_BY_PATH -SYS_CONTEXT -TAG_REFERENCES -TAG_REFERENCES_ALL_COLUMNS -TAG_REFERENCES_WITH_LINEAGE -TAN -TANH -TASK_DEPENDENTS -TASK_HISTORY -TEXT_HTML -TEXT_PLAIN -TIME -TIMEADD -TIMEDIFF -TIMESTAMPADD -TIMESTAMPDIFF -TIMESTAMP_FROM_PARTS -TIME_FROM_PARTS -TIME_SLICE -TO_ARRAY -TO_BINARY -TO_BOOLEAN -TO_CHAR -TO_DATE -TO_DECFLOAT -TO_DECIMAL -TO_DOUBLE -TO_FILE -TO_GEOGRAPHY -TO_GEOMETRY -TO_JSON -TO_NUMBER -TO_NUMERIC -TO_OBJECT -TO_QUERY -TO_TIME -TO_TIMESTAMP -TO_TIMESTAMP_LTZ -TO_TIMESTAMP_NTZ -TO_TIMESTAMP_TZ -TO_UUID -TO_VARCHAR -TO_VARIANT -TO_XML -TRANSFORM -TRANSLATE -TRIM -TRUNC -TRUNCATE -TRY_BASE64_DECODE_BINARY -TRY_BASE64_DECODE_STRING -TRY_CAST -TRY_COMPLETE -TRY_DECRYPT -TRY_DECRYPT_RAW -TRY_HEX_DECODE_BINARY -TRY_HEX_DECODE_STRING -TRY_PARSE_JSON -TRY_TO_BINARY -TRY_TO_BOOLEAN -TRY_TO_DATE -TRY_TO_DECFLOAT -TRY_TO_DECIMAL -TRY_TO_DOUBLE -TRY_TO_FILE -TRY_TO_GEOGRAPHY -TRY_TO_GEOMETRY -TRY_TO_NUMBER -TRY_TO_NUMERIC -TRY_TO_TIME -TRY_TO_TIMESTAMP -TRY_TO_TIMESTAMP_LTZ -TRY_TO_TIMESTAMP_NTZ -TRY_TO_TIMESTAMP_TZ -TRY_TO_UUID -TYPEOF -UNICODE -UNIFORM -UNIQUE_COUNT -UPPER -UUID_STRING -VALIDATE -VALIDATE_PIPE_LOAD -VARIANCE -VARIANCE_POP -VARIANCE_SAMP -VAR_POP -VAR_SAMP -VECTOR_AVG -VECTOR_COSINE_SIMILARITY -VECTOR_INNER_PRODUCT -VECTOR_L1_DISTANCE -VECTOR_L2_DISTANCE -VECTOR_MAX -VECTOR_MIN -VECTOR_NORMALIZE -VECTOR_SUM -VECTOR_TRUNCATE -WAREHOUSE_LOAD_HISTORY -WAREHOUSE_METERING_HISTORY -WEEK -WEEKISO -WEEKOFYEAR -WIDTH_BUCKET -XMLGET -YEAR -YEAROFWEEK -YEAROFWEEKISO -ZEROIFNULL -ZIPF diff --git a/crates/sqlbuild-analysis/src/semantic_validation/main/map_diagnostics.rs b/crates/sqlbuild-analysis/src/semantic_validation/main/map_diagnostics.rs index 73e179774e..75bfd83774 100644 --- a/crates/sqlbuild-analysis/src/semantic_validation/main/map_diagnostics.rs +++ b/crates/sqlbuild-analysis/src/semantic_validation/main/map_diagnostics.rs @@ -1,11 +1,9 @@ -use crate::semantic_validation::models::FunctionProbes; use polyglot_sql::{DialectType, ValidationResult}; pub(crate) fn map_diagnostics( sql: &str, dialect: DialectType, result: ValidationResult, - probes: &FunctionProbes, ) -> Result { - crate::semantic_validation::_helpers::diagnostics::map_diagnostics(sql, dialect, result, probes) + crate::semantic_validation::_helpers::diagnostics::map_diagnostics(sql, dialect, result) } diff --git a/crates/sqlbuild-analysis/src/semantic_validation/models.rs b/crates/sqlbuild-analysis/src/semantic_validation/models.rs index a24345c77a..a6f184a32c 100644 --- a/crates/sqlbuild-analysis/src/semantic_validation/models.rs +++ b/crates/sqlbuild-analysis/src/semantic_validation/models.rs @@ -1,6 +1,6 @@ //! Compile-owned native catalog, mapping state and boundary requests. -use crate::semantic_validation::types::{PreparedCompactAnalysis, ProbeKey, Relations}; +use crate::semantic_validation::types::{PreparedCompactAnalysis, Relations}; use polyglot_sql::validation::SchemaTable; use polyglot_sql::{DialectType, SchemaValidationOptions}; use rayon::ThreadPool; @@ -65,12 +65,6 @@ pub struct PositionInput { pub passes: Vec>, } -/// Memoized call-site function-name probes, owned by the catalog that validates a compile. -#[derive(Debug, Default)] -pub(crate) struct FunctionProbes { - pub(crate) suggestions: Mutex>>, -} - pub struct ProjectCatalog { pub(crate) dialect: DialectType, pub(crate) options: SchemaValidationOptions, @@ -78,7 +72,6 @@ pub struct ProjectCatalog { pub(super) tables: HashMap, pub(super) overrides: Vec>, pub(super) analysis_tables: HashMap, - pub(crate) function_probes: Arc, /// One analysis pool per compile catalog, built on first use and shared by its views. pub(super) analysis_pool: Arc>>>, /// Analysis batches running on the pool; preparation only borrows the pool while none run. diff --git a/crates/sqlbuild-analysis/src/semantic_validation/tests/test_function_names.rs b/crates/sqlbuild-analysis/src/semantic_validation/tests/test_function_names.rs index 11698e22b1..9003dcfa2a 100644 --- a/crates/sqlbuild-analysis/src/semantic_validation/tests/test_function_names.rs +++ b/crates/sqlbuild-analysis/src/semantic_validation/tests/test_function_names.rs @@ -1,6 +1,5 @@ use crate::semantic_validation::_helpers::diagnostics::map_diagnostics; use crate::semantic_validation::_helpers::function_names::unsupported_calls; -use crate::semantic_validation::models::FunctionProbes; use crate::semantic_validation::tests::test_types::{ LocatedFunctionDiagnosticTestCase, UnsupportedFunctionTestCase, }; @@ -11,25 +10,25 @@ fn given_unsupported_builtin_spelling_when_scanning_then_reports_dialect_spellin let test_cases = [ UnsupportedFunctionTestCase { description: "Snowflake STARTS_WITH suggests STARTSWITH", - dialect: DialectType::Snowflake, + dialect: "snowflake", sql: "SELECT STARTS_WITH(product_name, 'a') AS flagged FROM products", expected_calls: &[("STARTS_WITH", "STARTSWITH")], }, UnsupportedFunctionTestCase { description: "matching is case-insensitive and keeps the authored spelling", - dialect: DialectType::Snowflake, + dialect: "snowflake", sql: "select ends_with(product_name, 'a'), lcase(product_name) from products", expected_calls: &[("ends_with", "ENDSWITH"), ("lcase", "LOWER")], }, UnsupportedFunctionTestCase { description: "conditional and length aliases suggest the Snowflake spelling", - dialect: DialectType::Snowflake, + dialect: "snowflake", sql: "SELECT IF(quantity > 0, 1, 0), CHAR_LENGTH(product_name) FROM products", expected_calls: &[("IF", "IFF"), ("CHAR_LENGTH", "LENGTH")], }, UnsupportedFunctionTestCase { description: "calls inside windows, lambdas and table functions are checked", - dialect: DialectType::Snowflake, + dialect: "snowflake", sql: "SELECT SUM(quantity) OVER (PARTITION BY UCASE(category)), \ FILTER(tags, tag -> STARTS_WITH(tag, 'a')) \ FROM products, TABLE(FLATTEN(input => ARRAY_CONSTRUCT(LCASE(sku))))", @@ -41,26 +40,26 @@ fn given_unsupported_builtin_spelling_when_scanning_then_reports_dialect_spellin }, UnsupportedFunctionTestCase { description: "an object literal value after a string key is checked", - dialect: DialectType::Snowflake, + dialect: "snowflake", sql: "SELECT {'featured': STARTS_WITH(product_name, 'a')} FROM products", expected_calls: &[("STARTS_WITH", "STARTSWITH")], }, UnsupportedFunctionTestCase { description: "a MATCH_RECOGNIZE DEFINE condition after AS is checked", - dialect: DialectType::Snowflake, + dialect: "snowflake", sql: "SELECT * FROM products MATCH_RECOGNIZE (ORDER BY sku PATTERN (featured) \ DEFINE featured AS STARTS_WITH(product_name, 'a'))", expected_calls: &[("STARTS_WITH", "STARTSWITH")], }, UnsupportedFunctionTestCase { description: "comments between the name and its parenthesis are skipped", - dialect: DialectType::Snowflake, + dialect: "snowflake", sql: "SELECT LCASE /* lower */ (sku), UCASE -- upper\n (sku) FROM products", expected_calls: &[("LCASE", "LOWER"), ("UCASE", "UPPER")], }, UnsupportedFunctionTestCase { description: "operand FROM and select lists inside a FROM subquery are checked", - dialect: DialectType::Snowflake, + dialect: "snowflake", sql: "SELECT TRIM(BOTH 'x' FROM LCASE(sku)), sku IS DISTINCT FROM UCASE(sku) \ FROM (SELECT sku, LCASE(sku) AS lowered FROM products) AS listed, catalog \ WHERE sku IN (1, 2) AND LCASE(sku) = 'a'", @@ -73,19 +72,19 @@ fn given_unsupported_builtin_spelling_when_scanning_then_reports_dialect_spellin }, UnsupportedFunctionTestCase { description: "a call followed by AS NOT without MATERIALIZED is still checked", - dialect: DialectType::DuckDB, + dialect: "duckdb", sql: "SELECT STARTSWITH(product_name, 'a') AS not_featured FROM products", expected_calls: &[("STARTSWITH", "STARTS_WITH")], }, UnsupportedFunctionTestCase { description: "DuckDB STARTSWITH suggests the catalogue spelling", - dialect: DialectType::DuckDB, + dialect: "duckdb", sql: "SELECT STARTSWITH(product_name, 'a') FROM products", expected_calls: &[("STARTSWITH", "STARTS_WITH")], }, ]; for test_case in &test_cases { - let calls = unsupported_calls(test_case.sql, test_case.dialect, &FunctionProbes::default()); + let calls = unsupported_calls(test_case.sql, test_case.dialect); let observed: Vec<(&str, &str)> = calls .iter() .map(|call| { @@ -109,7 +108,7 @@ fn given_supported_or_non_builtin_call_when_scanning_then_reports_nothing() { let test_cases = [ UnsupportedFunctionTestCase { description: "listed Snowflake synonyms are accepted", - dialect: DialectType::Snowflake, + dialect: "snowflake", sql: "SELECT STARTSWITH(a, 'x'), ENDSWITH(a, 'x'), CONTAINS(a, 'x'), SUBSTR(a, 1, 2), \ SUBSTRING(a, 1, 2), TIMESTAMPDIFF(day, b, c), DATEDIFF(day, b, c), \ TRY_TO_DECIMAL(a, 10, 2), TRY_TO_NUMBER(a), IFF(d, 1, 0), LEN(a), \ @@ -119,14 +118,14 @@ fn given_supported_or_non_builtin_call_when_scanning_then_reports_nothing() { }, UnsupportedFunctionTestCase { description: "quoted identifiers, literals and comments are not call sites", - dialect: DialectType::Snowflake, + dialect: "snowflake", sql: "SELECT \"STARTS_WITH\"(a, 'x'), 'STARTS_WITH(a, b)', $$LCASE(a)$$ \ -- STARTS_WITH(a, 'x')\n /* LCASE(a) */ FROM products", expected_calls: &[], }, UnsupportedFunctionTestCase { description: "methods, qualified functions, sentinels and macros are not checked", - dialect: DialectType::Snowflake, + dialect: "snowflake", sql: "SELECT tools.starts_with(a, 'x'), analytics.helpers.lcase(a), \ __sqlbuild_udf_starts_with(a, 'x'), @py.starts_with(a), payload:lcase(a) \ FROM __ref(\"products\")", @@ -134,13 +133,13 @@ fn given_supported_or_non_builtin_call_when_scanning_then_reports_nothing() { }, UnsupportedFunctionTestCase { description: "unknown and user-defined function names are never flagged", - dialect: DialectType::Snowflake, + dialect: "snowflake", sql: "SELECT normalize_sku(a), DATE_ADD(day, 1, b), my_udf() FROM products", expected_calls: &[], }, UnsupportedFunctionTestCase { description: "type positions and keywords before parentheses are not calls", - dialect: DialectType::Snowflake, + dialect: "snowflake", sql: "WITH ranked (sku, position_rank) AS (SELECT DISTINCT (a), \ ROW_NUMBER() OVER (ORDER BY b) FROM products WHERE NOT (a IN (1, 2)) \ AND EXISTS (SELECT 1)) \ @@ -149,14 +148,14 @@ fn given_supported_or_non_builtin_call_when_scanning_then_reports_nothing() { }, UnsupportedFunctionTestCase { description: "CTE column lists are not calls", - dialect: DialectType::Snowflake, + dialect: "snowflake", sql: "WITH listed AS (SELECT 1 AS dow), day_of_week (dow) AS (SELECT 1), \ lcase(x) AS (SELECT 2) SELECT dow FROM day_of_week", expected_calls: &[], }, UnsupportedFunctionTestCase { description: "materialized CTE column lists are not calls", - dialect: DialectType::DuckDB, + dialect: "duckdb", sql: "WITH day_of_week (d) AS MATERIALIZED (SELECT 1), \ day_of_month (d) AS /* hint */ NOT -- keep\n MATERIALIZED (SELECT 2) \ SELECT * FROM day_of_week", @@ -164,7 +163,7 @@ fn given_supported_or_non_builtin_call_when_scanning_then_reports_nothing() { }, UnsupportedFunctionTestCase { description: "derived-table, table-function and table aliases are not calls", - dialect: DialectType::Snowflake, + dialect: "snowflake", sql: "SELECT x FROM (VALUES (1)) day_of_week (x), products lcase (y), \ TABLE(FLATTEN(input => tags)) ucase (z), day_of_month (w) \ JOIN char_length (v) ON TRUE", @@ -172,27 +171,27 @@ fn given_supported_or_non_builtin_call_when_scanning_then_reports_nothing() { }, UnsupportedFunctionTestCase { description: "INSERT and CREATE column lists are not calls", - dialect: DialectType::Snowflake, + dialect: "snowflake", sql: "CREATE TABLE day_of_week (dow INTEGER); CREATE VIEW lcase (x) AS SELECT 1; \ INSERT INTO ucase (x) SELECT 1; CREATE TABLE IF NOT EXISTS char_length (x INT)", expected_calls: &[], }, UnsupportedFunctionTestCase { description: "dialects without a spelling table are not checked", - dialect: DialectType::PostgreSQL, + dialect: "postgres", sql: "SELECT STARTS_WITH(a, 'x'), LCASE(a) FROM products", expected_calls: &[], }, UnsupportedFunctionTestCase { description: "DuckDB catalogue spellings are accepted", - dialect: DialectType::DuckDB, + dialect: "duckdb", sql: "SELECT starts_with(a, 'x'), prefix(a, 'x'), lcase(a), substr(a, 1, 2) \ FROM products", expected_calls: &[], }, ]; for test_case in &test_cases { - let calls = unsupported_calls(test_case.sql, test_case.dialect, &FunctionProbes::default()); + let calls = unsupported_calls(test_case.sql, test_case.dialect); let observed: Vec<(&str, &str)> = calls .iter() .map(|call| (call.name.as_str(), call.suggestion.as_str())) @@ -225,7 +224,6 @@ fn given_unsupported_spelling_when_mapping_diagnostics_then_returns_located_b101 valid: true, errors: Vec::new(), }, - &FunctionProbes::default(), )?; let [error] = result.errors.as_slice() else { return Err(format!("{}: {:?}", test_case.description, result.errors)); diff --git a/crates/sqlbuild-analysis/src/semantic_validation/tests/test_truth_tests.rs b/crates/sqlbuild-analysis/src/semantic_validation/tests/test_truth_tests.rs index f9ba73f68f..6b9865dd9b 100644 --- a/crates/sqlbuild-analysis/src/semantic_validation/tests/test_truth_tests.rs +++ b/crates/sqlbuild-analysis/src/semantic_validation/tests/test_truth_tests.rs @@ -1,6 +1,5 @@ use crate::semantic_validation::_helpers::diagnostics::map_diagnostics; use crate::semantic_validation::_helpers::truth_tests::{rejects_truth_tests, truth_tests}; -use crate::semantic_validation::models::FunctionProbes; use crate::semantic_validation::tests::test_types::{ LocatedTruthTestDiagnosticTestCase, TruthTestDialectTestCase, TruthTestScanTestCase, }; @@ -91,7 +90,6 @@ fn given_dialect_when_checking_truth_tests_then_only_rejecting_warehouses_report valid: true, errors: Vec::new(), }, - &FunctionProbes::default(), ) .expect(test_case.description); assert_eq!( @@ -162,7 +160,6 @@ fn given_rejected_truth_test_when_mapping_diagnostics_then_returns_located_b103( valid: true, errors: Vec::new(), }, - &FunctionProbes::default(), )?; let [error] = result.errors.as_slice() else { return Err(format!("{}: {:?}", test_case.description, result.errors)); diff --git a/crates/sqlbuild-analysis/src/semantic_validation/tests/test_types.rs b/crates/sqlbuild-analysis/src/semantic_validation/tests/test_types.rs index 201a5dc6fc..7fcc899e20 100644 --- a/crates/sqlbuild-analysis/src/semantic_validation/tests/test_types.rs +++ b/crates/sqlbuild-analysis/src/semantic_validation/tests/test_types.rs @@ -12,7 +12,7 @@ pub(crate) struct SemanticValidationBatchTestCase { pub(crate) struct UnsupportedFunctionTestCase { pub(crate) description: &'static str, - pub(crate) dialect: polyglot_sql::DialectType, + pub(crate) dialect: &'static str, pub(crate) sql: &'static str, pub(crate) expected_calls: &'static [(&'static str, &'static str)], } diff --git a/crates/sqlbuild-analysis/src/semantic_validation/types.rs b/crates/sqlbuild-analysis/src/semantic_validation/types.rs index 8ab94dd7a8..02480b2f00 100644 --- a/crates/sqlbuild-analysis/src/semantic_validation/types.rs +++ b/crates/sqlbuild-analysis/src/semantic_validation/types.rs @@ -5,7 +5,6 @@ pub type Relations = HashMap; pub type BindingRequest = (String, Vec<(String, bool)>, Relations); /// One analysis SQL with its relation stubs and placeholder defaults. pub type NormalizationRequest = (String, HashMap, HashMap); -pub(crate) type ProbeKey = (polyglot_sql::DialectType, String, usize); /// Compact analysis whose catalog schemas are resolved; it runs with an options-only catalog. pub(crate) type PreparedCompactAnalysis = Box Result + Send>; From c00a9da1ed720fb32dd4e396242fcbfe9b6ec524 Mon Sep 17 00:00:00 2001 From: Kevin Longe <273747162+kvlonge@users.noreply.github.com> Date: Sat, 7 Nov 2026 20:05:23 +0000 Subject: [PATCH 2/2] refactor(analysis): share diagnostic location and the catalog's options view --- .../semantic_validation/_helpers/catalog.rs | 7 +---- .../_helpers/function_names.rs | 26 ++++++++++++++----- .../_helpers/sqlite_temporal_casts.rs | 11 ++++---- .../_helpers/truth_tests.rs | 11 ++++---- 4 files changed, 31 insertions(+), 24 deletions(-) diff --git a/crates/sqlbuild-analysis/src/semantic_validation/_helpers/catalog.rs b/crates/sqlbuild-analysis/src/semantic_validation/_helpers/catalog.rs index 9dae17ad2e..9ac0e4b46d 100644 --- a/crates/sqlbuild-analysis/src/semantic_validation/_helpers/catalog.rs +++ b/crates/sqlbuild-analysis/src/semantic_validation/_helpers/catalog.rs @@ -175,14 +175,9 @@ impl ProjectCatalog { pub fn with_relations(&self, relations: Relations) -> Self { let mut catalog = Self { - dialect: self.dialect, - options: self.options.clone(), - quoted_ignore_case: self.quoted_ignore_case, tables: self.tables.clone(), - overrides: Vec::new(), analysis_tables: self.analysis_tables.clone(), - analysis_pool: Arc::clone(&self.analysis_pool), - running_analyses: Arc::clone(&self.running_analyses), + ..self.analysis_view() }; catalog.update_relations(relations); catalog diff --git a/crates/sqlbuild-analysis/src/semantic_validation/_helpers/function_names.rs b/crates/sqlbuild-analysis/src/semantic_validation/_helpers/function_names.rs index fe9a00ead3..0c697b9487 100644 --- a/crates/sqlbuild-analysis/src/semantic_validation/_helpers/function_names.rs +++ b/crates/sqlbuild-analysis/src/semantic_validation/_helpers/function_names.rs @@ -104,6 +104,21 @@ impl Cursor { } self.byte = byte; } + + /// `error` located at the byte range `start..end` of `sql`, with its character span. + pub(crate) fn locate( + &mut self, + sql: &str, + (start, end): (usize, usize), + error: ValidationError, + ) -> ValidationError { + self.advance(sql, start); + let first: usize = self.characters; + let last: usize = first + sql[start..end].chars().count(); + error + .with_location(self.line, self.column) + .with_span(Some(first), Some(last)) + } } /// Read `dialect SPELLING arguments ACCEPTED` rows; `#` starts a comment. @@ -156,12 +171,11 @@ pub(crate) fn unsupported_function_errors(sql: &str, dialect: DialectType) -> Ve "Unknown function '{}' for dialect {dialect:?}; did you mean {}?", call.name, call.suggestion ); - cursor.advance(sql, call.start); - let start: usize = cursor.characters; - let end: usize = start + sql[call.start..call.end].chars().count(); - ValidationError::error(message, UNKNOWN_FUNCTION) - .with_location(cursor.line, cursor.column) - .with_span(Some(start), Some(end)) + cursor.locate( + sql, + (call.start, call.end), + ValidationError::error(message, UNKNOWN_FUNCTION), + ) }) .collect() } diff --git a/crates/sqlbuild-analysis/src/semantic_validation/_helpers/sqlite_temporal_casts.rs b/crates/sqlbuild-analysis/src/semantic_validation/_helpers/sqlite_temporal_casts.rs index c3a37312e8..fe304fd8b0 100644 --- a/crates/sqlbuild-analysis/src/semantic_validation/_helpers/sqlite_temporal_casts.rs +++ b/crates/sqlbuild-analysis/src/semantic_validation/_helpers/sqlite_temporal_casts.rs @@ -35,12 +35,11 @@ pub(crate) fn sqlite_temporal_cast_warnings( '2026-01-01 10:00:00' becomes 2026", cast.type_name ); - cursor.advance(sql, cast.start); - let start: usize = cursor.characters; - let end: usize = start + sql[cast.start..cast.end].chars().count(); - ValidationError::warning(message, SQLITE_TEMPORAL_CAST_CODE) - .with_location(cursor.line, cursor.column) - .with_span(Some(start), Some(end)) + cursor.locate( + sql, + (cast.start, cast.end), + ValidationError::warning(message, SQLITE_TEMPORAL_CAST_CODE), + ) }) .collect() } diff --git a/crates/sqlbuild-analysis/src/semantic_validation/_helpers/truth_tests.rs b/crates/sqlbuild-analysis/src/semantic_validation/_helpers/truth_tests.rs index 896b12f20d..350eac4856 100644 --- a/crates/sqlbuild-analysis/src/semantic_validation/_helpers/truth_tests.rs +++ b/crates/sqlbuild-analysis/src/semantic_validation/_helpers/truth_tests.rs @@ -37,12 +37,11 @@ pub(crate) fn unsupported_truth_test_errors( spelling(&test), rewrite(dialect, &test) ); - cursor.advance(sql, test.start); - let start: usize = cursor.characters; - let end: usize = start + sql[test.start..test.end].chars().count(); - ValidationError::error(message, UNSUPPORTED_TRUTH_TEST_CODE) - .with_location(cursor.line, cursor.column) - .with_span(Some(start), Some(end)) + cursor.locate( + sql, + (test.start, test.end), + ValidationError::error(message, UNSUPPORTED_TRUTH_TEST_CODE), + ) }) .collect() }