1use crate::dialects::{Dialect, DialectType};
8use crate::error::{ValidationError, ValidationResult};
9use crate::expressions::{
10 Column, DataType, Expression, Function, Insert, JoinKind, OracleDataType, TableRef, Update,
11};
12use crate::function_catalog::FunctionCatalog;
13#[cfg(any(
14 feature = "function-catalog-clickhouse",
15 feature = "function-catalog-duckdb",
16 feature = "function-catalog-all-dialects"
17))]
18use crate::function_catalog::{
19 FunctionNameCase as CoreFunctionNameCase, FunctionSignature as CoreFunctionSignature,
20 HashMapFunctionCatalog,
21};
22use crate::function_registry::canonical_typed_function_name_upper;
23use crate::optimizer::annotate_types::annotate_types;
24use crate::optimizer::normalize_identifiers::{get_normalization_strategy, normalize_identifier};
25use crate::optimizer::qualify_columns::normalize_dotted_columns_in_scope;
26use crate::resolver::Resolver;
27use crate::schema::{MappingSchema, Schema as SqlSchema};
28use crate::scope::{build_scope, walk_in_scope};
29use crate::traversal::ExpressionWalk;
30use serde::{Deserialize, Serialize};
31use std::collections::{HashMap, HashSet};
32use std::sync::Arc;
33
34#[cfg(any(
35 feature = "function-catalog-clickhouse",
36 feature = "function-catalog-duckdb",
37 feature = "function-catalog-all-dialects"
38))]
39use std::sync::LazyLock;
40
41#[derive(Debug, Clone, Serialize, Deserialize)]
43#[serde(deny_unknown_fields)]
44pub struct SchemaColumn {
45 pub name: String,
47 #[serde(default, rename = "type")]
49 pub data_type: String,
50 #[serde(default)]
52 pub nullable: Option<bool>,
53 #[serde(default, rename = "primaryKey")]
55 pub primary_key: bool,
56 #[serde(default)]
58 pub unique: bool,
59 #[serde(default)]
61 pub references: Option<SchemaColumnReference>,
62}
63
64#[derive(Debug, Clone, Serialize, Deserialize)]
66#[serde(deny_unknown_fields)]
67pub struct SchemaColumnReference {
68 pub table: String,
70 pub column: String,
72 #[serde(default)]
74 pub schema: Option<String>,
75}
76
77#[derive(Debug, Clone, Serialize, Deserialize)]
79#[serde(deny_unknown_fields)]
80pub struct SchemaForeignKey {
81 #[serde(default)]
83 pub name: Option<String>,
84 pub columns: Vec<String>,
86 pub references: SchemaTableReference,
88}
89
90#[derive(Debug, Clone, Serialize, Deserialize)]
92#[serde(deny_unknown_fields)]
93pub struct SchemaTableReference {
94 pub table: String,
96 pub columns: Vec<String>,
98 #[serde(default)]
100 pub schema: Option<String>,
101}
102
103#[derive(Debug, Clone, Serialize, Deserialize)]
105#[serde(deny_unknown_fields)]
106pub struct SchemaTable {
107 pub name: String,
109 #[serde(default)]
111 pub schema: Option<String>,
112 pub columns: Vec<SchemaColumn>,
114 #[serde(default)]
116 pub aliases: Vec<String>,
117 #[serde(default, rename = "primaryKey")]
119 pub primary_key: Vec<String>,
120 #[serde(default, rename = "uniqueKeys")]
122 pub unique_keys: Vec<Vec<String>>,
123 #[serde(default, rename = "foreignKeys")]
125 pub foreign_keys: Vec<SchemaForeignKey>,
126}
127
128#[derive(Debug, Clone, Serialize, Deserialize)]
130#[serde(deny_unknown_fields)]
131pub struct ValidationSchema {
132 pub tables: Vec<SchemaTable>,
134 #[serde(default)]
136 pub strict: Option<bool>,
137}
138
139#[derive(Clone, Serialize, Deserialize, Default)]
143#[serde(deny_unknown_fields)]
144pub struct SchemaValidationOptions {
145 #[serde(default, alias = "complexityGuard")]
147 pub complexity_guard: Option<crate::ComplexityGuardOptions>,
148 #[serde(default, alias = "checkTypes")]
150 pub check_types: bool,
151 #[serde(default, alias = "checkReferences")]
153 pub check_references: bool,
154 #[serde(default)]
156 pub strict: Option<bool>,
157 #[serde(default)]
159 pub semantic: bool,
160 #[serde(default, alias = "strictSyntax")]
162 pub strict_syntax: bool,
163 #[serde(skip, default)]
165 pub function_catalog: Option<Arc<dyn FunctionCatalog>>,
166}
167
168#[cfg(any(
169 feature = "function-catalog-clickhouse",
170 feature = "function-catalog-duckdb",
171 feature = "function-catalog-all-dialects"
172))]
173fn to_core_name_case(
174 case: polyglot_sql_function_catalogs::FunctionNameCase,
175) -> CoreFunctionNameCase {
176 match case {
177 polyglot_sql_function_catalogs::FunctionNameCase::Insensitive => {
178 CoreFunctionNameCase::Insensitive
179 }
180 polyglot_sql_function_catalogs::FunctionNameCase::Sensitive => {
181 CoreFunctionNameCase::Sensitive
182 }
183 }
184}
185
186#[cfg(any(
187 feature = "function-catalog-clickhouse",
188 feature = "function-catalog-duckdb",
189 feature = "function-catalog-all-dialects"
190))]
191fn to_core_signatures(
192 signatures: Vec<polyglot_sql_function_catalogs::FunctionSignature>,
193) -> Vec<CoreFunctionSignature> {
194 signatures
195 .into_iter()
196 .map(|signature| CoreFunctionSignature {
197 min_arity: signature.min_arity,
198 max_arity: signature.max_arity,
199 })
200 .collect()
201}
202
203#[cfg(any(
204 feature = "function-catalog-clickhouse",
205 feature = "function-catalog-duckdb",
206 feature = "function-catalog-all-dialects"
207))]
208struct EmbeddedCatalogSink<'a> {
209 catalog: &'a mut HashMapFunctionCatalog,
210 dialect_cache: HashMap<&'static str, Option<DialectType>>,
211}
212
213#[cfg(any(
214 feature = "function-catalog-clickhouse",
215 feature = "function-catalog-duckdb",
216 feature = "function-catalog-all-dialects"
217))]
218impl<'a> EmbeddedCatalogSink<'a> {
219 fn resolve_dialect(&mut self, dialect: &'static str) -> Option<DialectType> {
220 if let Some(cached) = self.dialect_cache.get(dialect) {
221 return *cached;
222 }
223 let parsed = dialect.parse::<DialectType>().ok();
224 self.dialect_cache.insert(dialect, parsed);
225 parsed
226 }
227}
228
229#[cfg(any(
230 feature = "function-catalog-clickhouse",
231 feature = "function-catalog-duckdb",
232 feature = "function-catalog-all-dialects"
233))]
234impl<'a> polyglot_sql_function_catalogs::CatalogSink for EmbeddedCatalogSink<'a> {
235 fn set_dialect_name_case(
236 &mut self,
237 dialect: &'static str,
238 name_case: polyglot_sql_function_catalogs::FunctionNameCase,
239 ) {
240 if let Some(core_dialect) = self.resolve_dialect(dialect) {
241 self.catalog
242 .set_dialect_name_case(core_dialect, to_core_name_case(name_case));
243 }
244 }
245
246 fn set_function_name_case(
247 &mut self,
248 dialect: &'static str,
249 function_name: &str,
250 name_case: polyglot_sql_function_catalogs::FunctionNameCase,
251 ) {
252 if let Some(core_dialect) = self.resolve_dialect(dialect) {
253 self.catalog.set_function_name_case(
254 core_dialect,
255 function_name,
256 to_core_name_case(name_case),
257 );
258 }
259 }
260
261 fn register(
262 &mut self,
263 dialect: &'static str,
264 function_name: &str,
265 signatures: Vec<polyglot_sql_function_catalogs::FunctionSignature>,
266 ) {
267 if let Some(core_dialect) = self.resolve_dialect(dialect) {
268 self.catalog
269 .register(core_dialect, function_name, to_core_signatures(signatures));
270 }
271 }
272}
273
274#[cfg(any(
275 feature = "function-catalog-clickhouse",
276 feature = "function-catalog-duckdb",
277 feature = "function-catalog-all-dialects"
278))]
279fn embedded_function_catalog_arc() -> Arc<dyn FunctionCatalog> {
280 static EMBEDDED: LazyLock<Arc<HashMapFunctionCatalog>> = LazyLock::new(|| {
281 let mut catalog = HashMapFunctionCatalog::default();
282 let mut sink = EmbeddedCatalogSink {
283 catalog: &mut catalog,
284 dialect_cache: HashMap::new(),
285 };
286 polyglot_sql_function_catalogs::register_enabled_catalogs(&mut sink);
287 Arc::new(catalog)
288 });
289
290 EMBEDDED.clone()
291}
292
293#[cfg(any(
294 feature = "function-catalog-clickhouse",
295 feature = "function-catalog-duckdb",
296 feature = "function-catalog-all-dialects"
297))]
298fn default_embedded_function_catalog() -> Option<Arc<dyn FunctionCatalog>> {
299 Some(embedded_function_catalog_arc())
300}
301
302#[cfg(not(any(
303 feature = "function-catalog-clickhouse",
304 feature = "function-catalog-duckdb",
305 feature = "function-catalog-all-dialects"
306)))]
307fn default_embedded_function_catalog() -> Option<Arc<dyn FunctionCatalog>> {
308 None
309}
310
311pub mod validation_codes {
313 pub const E_PARSE_OR_OPTIONS: &str = "E000";
315 pub const E_UNKNOWN_TABLE: &str = "E200";
316 pub const E_UNKNOWN_COLUMN: &str = "E201";
317 pub const E_UNKNOWN_FUNCTION: &str = "E202";
318 pub const E_INVALID_FUNCTION_ARITY: &str = "E203";
319 pub const E_INVALID_GROUPING: &str = "E230";
320 pub const E_INVALID_AGGREGATE: &str = "E231";
321 pub const E_INVALID_WINDOW: &str = "E232";
322
323 pub const W_SELECT_STAR: &str = "W001";
324 pub const W_AGGREGATE_WITHOUT_GROUP_BY: &str = "W002";
325 pub const W_DISTINCT_ORDER_BY: &str = "W003";
326 pub const W_LIMIT_WITHOUT_ORDER_BY: &str = "W004";
327
328 pub const E_TYPE_MISMATCH: &str = "E210";
330 pub const E_INVALID_PREDICATE_TYPE: &str = "E211";
331 pub const E_INVALID_ARITHMETIC_TYPE: &str = "E212";
332 pub const E_INVALID_FUNCTION_ARGUMENT_TYPE: &str = "E213";
333 pub const E_INVALID_ASSIGNMENT_TYPE: &str = "E214";
334 pub const E_SETOP_TYPE_MISMATCH: &str = "E215";
335 pub const E_SETOP_ARITY_MISMATCH: &str = "E216";
336 pub const E_INCOMPATIBLE_COMPARISON_TYPES: &str = "E217";
337 pub const E_INVALID_CAST: &str = "E218";
338 pub const E_UNKNOWN_INFERRED_TYPE: &str = "E219";
339
340 pub const W_IMPLICIT_CAST_COMPARISON: &str = "W210";
341 pub const W_IMPLICIT_CAST_ARITHMETIC: &str = "W211";
342 pub const W_IMPLICIT_CAST_ASSIGNMENT: &str = "W212";
343 pub const W_LOSSY_CAST: &str = "W213";
344 pub const W_SETOP_IMPLICIT_COERCION: &str = "W214";
345 pub const W_PREDICATE_NULLABILITY: &str = "W215";
346 pub const W_FUNCTION_ARGUMENT_COERCION: &str = "W216";
347 pub const W_AGGREGATE_TYPE_COERCION: &str = "W217";
348 pub const W_POSSIBLE_OVERFLOW: &str = "W218";
349 pub const W_POSSIBLE_TRUNCATION: &str = "W219";
350
351 pub const E_INVALID_FOREIGN_KEY_REFERENCE: &str = "E220";
353 pub const E_AMBIGUOUS_COLUMN_REFERENCE: &str = "E221";
354 pub const E_UNRESOLVED_REFERENCE: &str = "E222";
355 pub const E_CTE_COLUMN_COUNT_MISMATCH: &str = "E223";
356 pub const E_MISSING_REFERENCE_TARGET: &str = "E224";
357
358 pub const W_CARTESIAN_JOIN: &str = "W220";
359 pub const W_JOIN_NOT_USING_DECLARED_REFERENCE: &str = "W221";
360 pub const W_WEAK_REFERENCE_INTEGRITY: &str = "W222";
361}
362
363#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
365#[serde(rename_all = "snake_case")]
366pub enum TypeFamily {
367 Unknown,
368 Boolean,
369 Integer,
370 Numeric,
371 String,
372 Binary,
373 Date,
374 Time,
375 Timestamp,
376 Interval,
377 Json,
378 Uuid,
379 Array,
380 Map,
381 Struct,
382}
383
384impl TypeFamily {
385 pub fn is_numeric(self) -> bool {
386 matches!(self, TypeFamily::Integer | TypeFamily::Numeric)
387 }
388
389 pub fn is_temporal(self) -> bool {
390 matches!(
391 self,
392 TypeFamily::Date | TypeFamily::Time | TypeFamily::Timestamp | TypeFamily::Interval
393 )
394 }
395}
396
397#[derive(Debug, Clone)]
398struct TableSchemaEntry {
399 columns: HashMap<String, TypeFamily>,
400 column_order: Vec<String>,
401}
402
403fn lower(s: &str) -> String {
404 s.to_lowercase()
405}
406
407fn split_type_args(data_type: &str) -> Option<(&str, &str)> {
408 let open = data_type.find('(')?;
409 if !data_type.ends_with(')') || open + 1 >= data_type.len() {
410 return None;
411 }
412 let base = data_type[..open].trim();
413 let inner = data_type[open + 1..data_type.len() - 1].trim();
414 Some((base, inner))
415}
416
417pub fn canonical_type_family(data_type: &str) -> TypeFamily {
419 let trimmed = data_type
420 .trim()
421 .trim_matches(|c| c == '"' || c == '\'' || c == '`');
422 if trimmed.is_empty() {
423 return TypeFamily::Unknown;
424 }
425
426 let normalized = trimmed
428 .split_whitespace()
429 .collect::<Vec<_>>()
430 .join(" ")
431 .to_lowercase();
432
433 if let Some((base, inner)) = split_type_args(&normalized) {
435 match base {
436 "nullable" | "lowcardinality" => return canonical_type_family(inner),
437 "array" | "list" => return TypeFamily::Array,
438 "map" => return TypeFamily::Map,
439 "struct" | "row" | "record" => return TypeFamily::Struct,
440 _ => {}
441 }
442 }
443
444 if normalized.starts_with("array<") || normalized.starts_with("list<") {
445 return TypeFamily::Array;
446 }
447 if normalized.starts_with("map<") {
448 return TypeFamily::Map;
449 }
450 if normalized.starts_with("struct<")
451 || normalized.starts_with("row<")
452 || normalized.starts_with("record<")
453 || normalized.starts_with("object<")
454 {
455 return TypeFamily::Struct;
456 }
457
458 if normalized.ends_with("[]") {
459 return TypeFamily::Array;
460 }
461
462 let mut base = normalized
464 .split('(')
465 .next()
466 .unwrap_or("")
467 .trim()
468 .to_string();
469 if base.is_empty() {
470 return TypeFamily::Unknown;
471 }
472
473 base = base.strip_prefix("unsigned ").unwrap_or(&base).to_string();
474 base = base.strip_suffix(" unsigned").unwrap_or(&base).to_string();
475
476 match base.as_str() {
477 "bool" | "boolean" => TypeFamily::Boolean,
478 "tinyint" | "smallint" | "int2" | "int" | "integer" | "int4" | "int8" | "bigint"
479 | "serial" | "smallserial" | "bigserial" | "utinyint" | "usmallint" | "uinteger"
480 | "ubigint" | "uint8" | "uint16" | "uint32" | "uint64" | "int16" | "int32" | "int64"
481 | "hugeint" | "int128" | "largeint" | "uhugeint" | "uint128" => TypeFamily::Integer,
482 "numeric" | "decimal" | "dec" | "number" | "float" | "float4" | "float8" | "real"
483 | "double" | "double precision" | "bfloat16" | "float16" | "float32" | "float64" => {
484 TypeFamily::Numeric
485 }
486 "char" | "character" | "varchar" | "character varying" | "nchar" | "nvarchar" | "text"
487 | "string" | "clob" => TypeFamily::String,
488 "binary" | "varbinary" | "blob" | "bytea" | "bytes" => TypeFamily::Binary,
489 "date" => TypeFamily::Date,
490 "time" => TypeFamily::Time,
491 "timestamp"
492 | "timestamptz"
493 | "datetime"
494 | "datetime2"
495 | "smalldatetime"
496 | "timestamp with time zone"
497 | "timestamp without time zone" => TypeFamily::Timestamp,
498 "interval" => TypeFamily::Interval,
499 "json" | "jsonb" | "variant" => TypeFamily::Json,
500 "uuid" | "uniqueidentifier" => TypeFamily::Uuid,
501 "array" | "list" => TypeFamily::Array,
502 "map" => TypeFamily::Map,
503 "struct" | "row" | "record" | "object" => TypeFamily::Struct,
504 _ => TypeFamily::Unknown,
505 }
506}
507
508fn build_schema_map(schema: &ValidationSchema) -> HashMap<String, TableSchemaEntry> {
509 let mut map = HashMap::new();
510
511 for table in &schema.tables {
512 let column_order: Vec<String> = table.columns.iter().map(|c| lower(&c.name)).collect();
513 let columns: HashMap<String, TypeFamily> = table
514 .columns
515 .iter()
516 .map(|c| (lower(&c.name), canonical_type_family(&c.data_type)))
517 .collect();
518 let entry = TableSchemaEntry {
519 columns,
520 column_order,
521 };
522
523 let simple_name = lower(&table.name);
524 map.insert(simple_name, entry.clone());
525
526 if let Some(table_schema) = &table.schema {
527 map.insert(
528 format!("{}.{}", lower(table_schema), lower(&table.name)),
529 entry.clone(),
530 );
531 }
532
533 for alias in &table.aliases {
534 map.insert(lower(alias), entry.clone());
535 }
536 }
537
538 map
539}
540
541fn type_family_to_data_type(family: TypeFamily) -> DataType {
542 match family {
543 TypeFamily::Unknown => DataType::Unknown,
544 TypeFamily::Boolean => DataType::Boolean,
545 TypeFamily::Integer => DataType::Int {
546 length: None,
547 integer_spelling: false,
548 },
549 TypeFamily::Numeric => DataType::Double {
550 precision: None,
551 scale: None,
552 },
553 TypeFamily::String => DataType::VarChar {
554 length: None,
555 parenthesized_length: false,
556 },
557 TypeFamily::Binary => DataType::VarBinary { length: None },
558 TypeFamily::Date => DataType::Date,
559 TypeFamily::Time => DataType::Time {
560 precision: None,
561 timezone: false,
562 },
563 TypeFamily::Timestamp => DataType::Timestamp {
564 precision: None,
565 timezone: false,
566 },
567 TypeFamily::Interval => DataType::Interval {
568 unit: None,
569 to: None,
570 },
571 TypeFamily::Json => DataType::Json,
572 TypeFamily::Uuid => DataType::Uuid,
573 TypeFamily::Array => DataType::Array {
574 element_type: Box::new(DataType::Unknown),
575 dimension: None,
576 },
577 TypeFamily::Map => DataType::Map {
578 key_type: Box::new(DataType::Unknown),
579 value_type: Box::new(DataType::Unknown),
580 },
581 TypeFamily::Struct => DataType::Struct {
582 fields: Vec::new(),
583 nested: false,
584 },
585 }
586}
587
588fn build_resolver_schema(schema: &ValidationSchema) -> MappingSchema {
589 let mut mapping = MappingSchema::new();
590
591 for table in &schema.tables {
592 let columns: Vec<(String, DataType)> = table
593 .columns
594 .iter()
595 .map(|column| {
596 (
597 lower(&column.name),
598 type_family_to_data_type(canonical_type_family(&column.data_type)),
599 )
600 })
601 .collect();
602
603 let mut table_names = Vec::new();
604 table_names.push(lower(&table.name));
605 if let Some(table_schema) = &table.schema {
606 table_names.push(format!("{}.{}", lower(table_schema), lower(&table.name)));
607 }
608 for alias in &table.aliases {
609 table_names.push(lower(alias));
610 }
611
612 let mut dedup = HashSet::new();
613 for table_name in table_names {
614 if dedup.insert(table_name.clone()) {
615 let _ = mapping.add_table(&table_name, &columns, None);
616 }
617 }
618 }
619
620 mapping
621}
622
623pub fn mapping_schema_from_validation_schema(schema: &ValidationSchema) -> MappingSchema {
629 build_resolver_schema(schema)
630}
631
632pub fn mapping_schema_from_validation_schema_with_dialect(
637 schema: &ValidationSchema,
638 dialect: DialectType,
639) -> MappingSchema {
640 let broad_schema = build_resolver_schema(schema);
641 let dialect_impl = Dialect::get(dialect);
642 let mut mapping = MappingSchema::with_dialect(dialect);
643
644 for table in &schema.tables {
645 let fallback_table = lower(&table.name);
646 let columns: Vec<(String, DataType)> = table
647 .columns
648 .iter()
649 .map(|column| {
650 let data_type = dialect_impl
651 .parse_data_type(column.data_type.trim())
652 .unwrap_or_else(|_| {
653 broad_schema
654 .get_column_type(&fallback_table, &column.name)
655 .unwrap_or(DataType::Unknown)
656 });
657 (column.name.clone(), data_type)
658 })
659 .collect();
660
661 let mut table_names = vec![table.name.clone()];
662 if let Some(table_schema) = &table.schema {
663 table_names.push(format!("{}.{}", table_schema, table.name));
664 }
665 table_names.extend(table.aliases.iter().cloned());
666
667 let mut seen = HashSet::new();
668 for table_name in table_names {
669 if seen.insert(lower(&table_name)) {
670 let _ = mapping.add_table(&table_name, &columns, Some(dialect));
671 }
672 }
673 }
674
675 mapping
676}
677
678fn table_ref_candidates(table: &TableRef) -> Vec<String> {
679 let name = lower(&table.name.name);
680 let schema = table.schema.as_ref().map(|s| lower(&s.name));
681 let catalog = table.catalog.as_ref().map(|c| lower(&c.name));
682
683 let mut candidates = Vec::new();
684 if let (Some(catalog), Some(schema)) = (&catalog, &schema) {
685 candidates.push(format!("{}.{}.{}", catalog, schema, name));
686 }
687 if let Some(schema) = &schema {
688 candidates.push(format!("{}.{}", schema, name));
689 }
690 candidates.push(name);
691 candidates
692}
693
694fn table_ref_display_name(table: &TableRef) -> String {
695 let mut parts = Vec::new();
696 if let Some(catalog) = &table.catalog {
697 parts.push(catalog.name.clone());
698 }
699 if let Some(schema) = &table.schema {
700 parts.push(schema.name.clone());
701 }
702 parts.push(table.name.name.clone());
703 parts.join(".")
704}
705
706#[derive(Debug, Default, Clone)]
707struct TypeCheckContext {
708 referenced_tables: HashSet<String>,
709 table_aliases: HashMap<String, String>,
710}
711
712fn type_family_name(family: TypeFamily) -> &'static str {
713 match family {
714 TypeFamily::Unknown => "unknown",
715 TypeFamily::Boolean => "boolean",
716 TypeFamily::Integer => "integer",
717 TypeFamily::Numeric => "numeric",
718 TypeFamily::String => "string",
719 TypeFamily::Binary => "binary",
720 TypeFamily::Date => "date",
721 TypeFamily::Time => "time",
722 TypeFamily::Timestamp => "timestamp",
723 TypeFamily::Interval => "interval",
724 TypeFamily::Json => "json",
725 TypeFamily::Uuid => "uuid",
726 TypeFamily::Array => "array",
727 TypeFamily::Map => "map",
728 TypeFamily::Struct => "struct",
729 }
730}
731
732fn is_string_like(family: TypeFamily) -> bool {
733 matches!(family, TypeFamily::String)
734}
735
736fn is_string_or_binary(family: TypeFamily) -> bool {
737 matches!(family, TypeFamily::String | TypeFamily::Binary)
738}
739
740fn type_issue(
741 strict: bool,
742 error_code: &str,
743 warning_code: &str,
744 message: impl Into<String>,
745) -> ValidationError {
746 if strict {
747 ValidationError::error(message.into(), error_code)
748 } else {
749 ValidationError::warning(message.into(), warning_code)
750 }
751}
752
753fn data_type_family(data_type: &DataType) -> TypeFamily {
754 match data_type {
755 DataType::Vertica { vertica_type } => match vertica_type.as_ref() {
756 crate::expressions::VerticaDataType::Array { .. }
757 | crate::expressions::VerticaDataType::Set { .. } => TypeFamily::Array,
758 crate::expressions::VerticaDataType::Row { .. } => TypeFamily::Struct,
759 crate::expressions::VerticaDataType::LongVarBinary { .. } => TypeFamily::Binary,
760 crate::expressions::VerticaDataType::Interval { .. } => TypeFamily::Interval,
761 },
762 DataType::Boolean => TypeFamily::Boolean,
763 DataType::TinyInt { .. }
764 | DataType::SmallInt { .. }
765 | DataType::Int { .. }
766 | DataType::BigInt { .. }
767 | DataType::Int128
768 | DataType::UInt8
769 | DataType::UInt16
770 | DataType::UInt32
771 | DataType::UInt64
772 | DataType::UInt128 => TypeFamily::Integer,
773 DataType::Float { .. } | DataType::Double { .. } | DataType::Decimal { .. } => {
774 TypeFamily::Numeric
775 }
776 DataType::Hana { hana_type } => match hana_type.name.as_str() {
777 "TINYINT" | "SMALLINT" | "INT" | "BIGINT" => TypeFamily::Integer,
778 "TIMESTAMP" | "SECONDDATE" => TypeFamily::Timestamp,
779 "TIME" => TypeFamily::Time,
780 "FLOAT" | "DECIMAL" | "SMALLDECIMAL" => TypeFamily::Numeric,
781 "BINTEXT" => TypeFamily::Binary,
782 "ALPHANUM" | "VARCHAR" | "NVARCHAR" | "CLOB" | "NCLOB" | "TEXT" | "SHORTTEXT" => {
783 TypeFamily::String
784 }
785 _ => TypeFamily::Unknown,
786 },
787 DataType::Oracle { oracle_type } => match oracle_type {
788 OracleDataType::Number { .. }
789 | OracleDataType::BinaryFloat
790 | OracleDataType::BinaryDouble
791 | OracleDataType::Float { .. } => TypeFamily::Numeric,
792 OracleDataType::Character { .. }
793 | OracleDataType::Clob { .. }
794 | OracleDataType::Long { raw: false }
795 | OracleDataType::RowId => TypeFamily::String,
796 OracleDataType::Blob
797 | OracleDataType::Raw { .. }
798 | OracleDataType::Long { raw: true } => TypeFamily::Binary,
799 OracleDataType::Date => TypeFamily::Date,
800 OracleDataType::Timestamp { .. } => TypeFamily::Timestamp,
801 OracleDataType::IntervalYearToMonth { .. }
802 | OracleDataType::IntervalDayToSecond { .. } => TypeFamily::Interval,
803 },
804 DataType::Char { .. }
805 | DataType::VarChar { .. }
806 | DataType::String { .. }
807 | DataType::Text
808 | DataType::TextWithLength { .. }
809 | DataType::CharacterSet { .. } => TypeFamily::String,
810 DataType::Binary { .. } | DataType::VarBinary { .. } | DataType::Blob => TypeFamily::Binary,
811 DataType::Date => TypeFamily::Date,
812 DataType::Time { .. } => TypeFamily::Time,
813 DataType::Timestamp { .. } => TypeFamily::Timestamp,
814 DataType::Interval { .. } => TypeFamily::Interval,
815 DataType::Json | DataType::JsonB => TypeFamily::Json,
816 DataType::Uuid => TypeFamily::Uuid,
817 DataType::Array { .. } | DataType::List { .. } => TypeFamily::Array,
818 DataType::Map { .. } => TypeFamily::Map,
819 DataType::Struct { .. } | DataType::Object { .. } | DataType::Union { .. } => {
820 TypeFamily::Struct
821 }
822 DataType::Nullable { inner } => data_type_family(inner),
823 DataType::Custom { name } => canonical_type_family(name),
824 DataType::Unknown => TypeFamily::Unknown,
825 DataType::Bit { .. } | DataType::VarBit { .. } => TypeFamily::Binary,
826 DataType::Enum { .. } | DataType::Set { .. } => TypeFamily::String,
827 DataType::Vector { .. } => TypeFamily::Array,
828 DataType::Geometry { .. } | DataType::Geography { .. } => TypeFamily::Struct,
829 }
830}
831
832fn collect_type_check_context(
833 stmt: &Expression,
834 schema_map: &HashMap<String, TableSchemaEntry>,
835) -> TypeCheckContext {
836 fn add_table_to_context(
837 table: &TableRef,
838 schema_map: &HashMap<String, TableSchemaEntry>,
839 context: &mut TypeCheckContext,
840 ) {
841 let resolved_key = table_ref_candidates(table)
842 .into_iter()
843 .find(|k| schema_map.contains_key(k));
844
845 let Some(table_key) = resolved_key else {
846 return;
847 };
848
849 context.referenced_tables.insert(table_key.clone());
850 context
851 .table_aliases
852 .insert(lower(&table.name.name), table_key.clone());
853 if let Some(alias) = &table.alias {
854 context
855 .table_aliases
856 .insert(lower(&alias.name), table_key.clone());
857 }
858 }
859
860 let mut context = TypeCheckContext::default();
861 let cte_aliases: HashSet<_> = build_scope(stmt)
862 .cte_sources
863 .keys()
864 .map(|name| lower(name))
865 .collect();
866
867 for node in walk_in_scope(stmt, false) {
868 let Expression::Table(table) = node else {
869 continue;
870 };
871
872 if cte_aliases.contains(&lower(&table.name.name)) {
873 continue;
874 }
875
876 add_table_to_context(table, schema_map, &mut context);
877 }
878
879 match stmt {
882 Expression::Insert(insert) => {
883 add_table_to_context(&insert.table, schema_map, &mut context);
884 }
885 Expression::Update(update) => {
886 add_table_to_context(&update.table, schema_map, &mut context);
887 for table in &update.extra_tables {
888 add_table_to_context(table, schema_map, &mut context);
889 }
890 }
891 Expression::Delete(delete) => {
892 add_table_to_context(&delete.table, schema_map, &mut context);
893 for table in &delete.using {
894 add_table_to_context(table, schema_map, &mut context);
895 }
896 for table in &delete.tables {
897 add_table_to_context(table, schema_map, &mut context);
898 }
899 }
900 _ => {}
901 }
902
903 context
904}
905
906fn resolve_table_schema_entry<'a>(
907 table: &TableRef,
908 schema_map: &'a HashMap<String, TableSchemaEntry>,
909) -> Option<(String, &'a TableSchemaEntry)> {
910 let key = table_ref_candidates(table)
911 .into_iter()
912 .find(|k| schema_map.contains_key(k))?;
913 let entry = schema_map.get(&key)?;
914 Some((key, entry))
915}
916
917fn reference_issue(strict: bool, message: impl Into<String>) -> ValidationError {
918 if strict {
919 ValidationError::error(
920 message.into(),
921 validation_codes::E_INVALID_FOREIGN_KEY_REFERENCE,
922 )
923 } else {
924 ValidationError::warning(message.into(), validation_codes::W_WEAK_REFERENCE_INTEGRITY)
925 }
926}
927
928fn reference_table_candidates(
929 table_name: &str,
930 explicit_schema: Option<&str>,
931 source_schema: Option<&str>,
932) -> Vec<String> {
933 let mut candidates = Vec::new();
934 let raw = lower(table_name);
935
936 if let Some(schema) = explicit_schema {
937 candidates.push(format!("{}.{}", lower(schema), raw));
938 }
939
940 if raw.contains('.') {
941 candidates.push(raw.clone());
942 if let Some(last) = raw.rsplit('.').next() {
943 candidates.push(last.to_string());
944 }
945 } else {
946 if let Some(schema) = source_schema {
947 candidates.push(format!("{}.{}", lower(schema), raw));
948 }
949 candidates.push(raw);
950 }
951
952 let mut dedup = HashSet::new();
953 candidates
954 .into_iter()
955 .filter(|c| dedup.insert(c.clone()))
956 .collect()
957}
958
959fn resolve_reference_table_key(
960 table_name: &str,
961 explicit_schema: Option<&str>,
962 source_schema: Option<&str>,
963 schema_map: &HashMap<String, TableSchemaEntry>,
964) -> Option<String> {
965 reference_table_candidates(table_name, explicit_schema, source_schema)
966 .into_iter()
967 .find(|candidate| schema_map.contains_key(candidate))
968}
969
970fn key_types_compatible(source: TypeFamily, target: TypeFamily) -> bool {
971 if source == TypeFamily::Unknown || target == TypeFamily::Unknown {
972 return true;
973 }
974 if source == target {
975 return true;
976 }
977 if source.is_numeric() && target.is_numeric() {
978 return true;
979 }
980 if source.is_temporal() && target.is_temporal() {
981 return true;
982 }
983 false
984}
985
986fn table_key_hints(table: &SchemaTable) -> HashSet<String> {
987 let mut hints = HashSet::new();
988 for column in &table.columns {
989 if column.primary_key || column.unique {
990 hints.insert(lower(&column.name));
991 }
992 }
993 for key_col in &table.primary_key {
994 hints.insert(lower(key_col));
995 }
996 for group in &table.unique_keys {
997 if group.len() == 1 {
998 if let Some(col) = group.first() {
999 hints.insert(lower(col));
1000 }
1001 }
1002 }
1003 hints
1004}
1005
1006fn check_reference_integrity(
1007 schema: &ValidationSchema,
1008 schema_map: &HashMap<String, TableSchemaEntry>,
1009 strict: bool,
1010) -> Vec<ValidationError> {
1011 let mut errors = Vec::new();
1012
1013 let mut key_hints_lookup: HashMap<String, HashSet<String>> = HashMap::new();
1014 for table in &schema.tables {
1015 let simple = lower(&table.name);
1016 key_hints_lookup.insert(simple, table_key_hints(table));
1017 if let Some(schema_name) = &table.schema {
1018 let qualified = format!("{}.{}", lower(schema_name), lower(&table.name));
1019 key_hints_lookup.insert(qualified, table_key_hints(table));
1020 }
1021 }
1022
1023 for table in &schema.tables {
1024 let source_table_display = if let Some(schema_name) = &table.schema {
1025 format!("{}.{}", schema_name, table.name)
1026 } else {
1027 table.name.clone()
1028 };
1029 let source_schema = table.schema.as_deref();
1030 let source_columns: HashMap<String, TypeFamily> = table
1031 .columns
1032 .iter()
1033 .map(|col| (lower(&col.name), canonical_type_family(&col.data_type)))
1034 .collect();
1035
1036 for source_col in &table.columns {
1037 let Some(reference) = &source_col.references else {
1038 continue;
1039 };
1040 let source_type = canonical_type_family(&source_col.data_type);
1041
1042 let Some(target_key) = resolve_reference_table_key(
1043 &reference.table,
1044 reference.schema.as_deref(),
1045 source_schema,
1046 schema_map,
1047 ) else {
1048 errors.push(reference_issue(
1049 strict,
1050 format!(
1051 "Foreign key reference '{}.{}' points to unknown table '{}'",
1052 source_table_display, source_col.name, reference.table
1053 ),
1054 ));
1055 continue;
1056 };
1057
1058 let target_column = lower(&reference.column);
1059 let Some(target_entry) = schema_map.get(&target_key) else {
1060 errors.push(reference_issue(
1061 strict,
1062 format!(
1063 "Foreign key reference '{}.{}' points to unknown table '{}'",
1064 source_table_display, source_col.name, reference.table
1065 ),
1066 ));
1067 continue;
1068 };
1069
1070 let Some(target_type) = target_entry.columns.get(&target_column).copied() else {
1071 errors.push(reference_issue(
1072 strict,
1073 format!(
1074 "Foreign key reference '{}.{}' points to unknown column '{}.{}'",
1075 source_table_display, source_col.name, target_key, reference.column
1076 ),
1077 ));
1078 continue;
1079 };
1080
1081 if !key_types_compatible(source_type, target_type) {
1082 errors.push(reference_issue(
1083 strict,
1084 format!(
1085 "Foreign key type mismatch for '{}.{}' -> '{}.{}': {} vs {}",
1086 source_table_display,
1087 source_col.name,
1088 target_key,
1089 reference.column,
1090 type_family_name(source_type),
1091 type_family_name(target_type)
1092 ),
1093 ));
1094 }
1095
1096 if let Some(target_key_hints) = key_hints_lookup.get(&target_key) {
1097 if !target_key_hints.contains(&target_column) {
1098 errors.push(ValidationError::warning(
1099 format!(
1100 "Referenced column '{}.{}' is not marked as primary/unique key",
1101 target_key, reference.column
1102 ),
1103 validation_codes::W_WEAK_REFERENCE_INTEGRITY,
1104 ));
1105 }
1106 }
1107 }
1108
1109 for foreign_key in &table.foreign_keys {
1110 if foreign_key.columns.is_empty() || foreign_key.references.columns.is_empty() {
1111 errors.push(reference_issue(
1112 strict,
1113 format!(
1114 "Table-level foreign key on '{}' has empty source or target column list",
1115 source_table_display
1116 ),
1117 ));
1118 continue;
1119 }
1120 if foreign_key.columns.len() != foreign_key.references.columns.len() {
1121 errors.push(reference_issue(
1122 strict,
1123 format!(
1124 "Table-level foreign key on '{}' has {} source columns but {} target columns",
1125 source_table_display,
1126 foreign_key.columns.len(),
1127 foreign_key.references.columns.len()
1128 ),
1129 ));
1130 continue;
1131 }
1132
1133 let Some(target_key) = resolve_reference_table_key(
1134 &foreign_key.references.table,
1135 foreign_key.references.schema.as_deref(),
1136 source_schema,
1137 schema_map,
1138 ) else {
1139 errors.push(reference_issue(
1140 strict,
1141 format!(
1142 "Table-level foreign key on '{}' points to unknown table '{}'",
1143 source_table_display, foreign_key.references.table
1144 ),
1145 ));
1146 continue;
1147 };
1148
1149 let Some(target_entry) = schema_map.get(&target_key) else {
1150 errors.push(reference_issue(
1151 strict,
1152 format!(
1153 "Table-level foreign key on '{}' points to unknown table '{}'",
1154 source_table_display, foreign_key.references.table
1155 ),
1156 ));
1157 continue;
1158 };
1159
1160 for (source_col, target_col) in foreign_key
1161 .columns
1162 .iter()
1163 .zip(foreign_key.references.columns.iter())
1164 {
1165 let source_col_name = lower(source_col);
1166 let target_col_name = lower(target_col);
1167
1168 let Some(source_type) = source_columns.get(&source_col_name).copied() else {
1169 errors.push(reference_issue(
1170 strict,
1171 format!(
1172 "Table-level foreign key on '{}' references unknown source column '{}'",
1173 source_table_display, source_col
1174 ),
1175 ));
1176 continue;
1177 };
1178
1179 let Some(target_type) = target_entry.columns.get(&target_col_name).copied() else {
1180 errors.push(reference_issue(
1181 strict,
1182 format!(
1183 "Table-level foreign key on '{}' references unknown target column '{}.{}'",
1184 source_table_display, target_key, target_col
1185 ),
1186 ));
1187 continue;
1188 };
1189
1190 if !key_types_compatible(source_type, target_type) {
1191 errors.push(reference_issue(
1192 strict,
1193 format!(
1194 "Table-level foreign key type mismatch '{}.{}' -> '{}.{}': {} vs {}",
1195 source_table_display,
1196 source_col,
1197 target_key,
1198 target_col,
1199 type_family_name(source_type),
1200 type_family_name(target_type)
1201 ),
1202 ));
1203 }
1204
1205 if let Some(target_key_hints) = key_hints_lookup.get(&target_key) {
1206 if !target_key_hints.contains(&target_col_name) {
1207 errors.push(ValidationError::warning(
1208 format!(
1209 "Referenced column '{}.{}' is not marked as primary/unique key",
1210 target_key, target_col
1211 ),
1212 validation_codes::W_WEAK_REFERENCE_INTEGRITY,
1213 ));
1214 }
1215 }
1216 }
1217 }
1218 }
1219
1220 errors
1221}
1222
1223fn infer_expression_type_family(
1224 expr: &Expression,
1225 schema_map: &HashMap<String, TableSchemaEntry>,
1226 context: &TypeCheckContext,
1227) -> TypeFamily {
1228 if let Some(data_type) = expr.inferred_type() {
1231 return data_type_family(data_type);
1232 }
1233 infer_expression_type_family_fallback(expr, schema_map, context)
1234}
1235
1236fn infer_expression_type_family_fallback(
1237 expr: &Expression,
1238 schema_map: &HashMap<String, TableSchemaEntry>,
1239 context: &TypeCheckContext,
1240) -> TypeFamily {
1241 match expr {
1242 Expression::Literal(literal) => match literal.as_ref() {
1243 crate::expressions::Literal::Number(value) => {
1244 if value.contains('.') || value.contains('e') || value.contains('E') {
1245 TypeFamily::Numeric
1246 } else {
1247 TypeFamily::Integer
1248 }
1249 }
1250 crate::expressions::Literal::HexNumber(_) => TypeFamily::Integer,
1251 crate::expressions::Literal::Date(_) => TypeFamily::Date,
1252 crate::expressions::Literal::Time(_) => TypeFamily::Time,
1253 crate::expressions::Literal::Timestamp(_)
1254 | crate::expressions::Literal::Datetime(_) => TypeFamily::Timestamp,
1255 crate::expressions::Literal::HexString(_)
1256 | crate::expressions::Literal::BitString(_)
1257 | crate::expressions::Literal::ByteString(_) => TypeFamily::Binary,
1258 _ => TypeFamily::String,
1259 },
1260 Expression::Boolean(_) => TypeFamily::Boolean,
1261 Expression::Null(_) => TypeFamily::Unknown,
1262 Expression::Column(_) => TypeFamily::Unknown,
1265 Expression::Cast(cast) | Expression::TryCast(cast) | Expression::SafeCast(cast) => {
1266 data_type_family(&cast.to)
1267 }
1268 Expression::Alias(alias) => {
1269 infer_expression_type_family_fallback(&alias.this, schema_map, context)
1270 }
1271 Expression::Neg(unary) => {
1272 infer_expression_type_family_fallback(&unary.this, schema_map, context)
1273 }
1274 Expression::Add(op) | Expression::Sub(op) | Expression::Mul(op) => {
1275 let left = infer_expression_type_family_fallback(&op.left, schema_map, context);
1276 let right = infer_expression_type_family_fallback(&op.right, schema_map, context);
1277 if left == TypeFamily::Unknown || right == TypeFamily::Unknown {
1278 TypeFamily::Unknown
1279 } else if left == TypeFamily::Integer && right == TypeFamily::Integer {
1280 TypeFamily::Integer
1281 } else if left.is_numeric() && right.is_numeric() {
1282 TypeFamily::Numeric
1283 } else if left.is_temporal() || right.is_temporal() {
1284 left
1285 } else {
1286 TypeFamily::Unknown
1287 }
1288 }
1289 Expression::Div(_) | Expression::Mod(_) => TypeFamily::Numeric,
1290 Expression::Concat(_) => TypeFamily::String,
1291 Expression::Eq(_)
1292 | Expression::Neq(_)
1293 | Expression::Lt(_)
1294 | Expression::Lte(_)
1295 | Expression::Gt(_)
1296 | Expression::Gte(_)
1297 | Expression::Like(_)
1298 | Expression::ILike(_)
1299 | Expression::And(_)
1300 | Expression::Or(_)
1301 | Expression::Not(_)
1302 | Expression::Between(_)
1303 | Expression::In(_)
1304 | Expression::IsNull(_)
1305 | Expression::IsTrue(_)
1306 | Expression::IsFalse(_)
1307 | Expression::Is(_) => TypeFamily::Boolean,
1308 Expression::Length(_) => TypeFamily::Integer,
1309 Expression::Upper(_)
1310 | Expression::Lower(_)
1311 | Expression::Trim(_)
1312 | Expression::LTrim(_)
1313 | Expression::RTrim(_)
1314 | Expression::Replace(_)
1315 | Expression::Substring(_)
1316 | Expression::Left(_)
1317 | Expression::Right(_)
1318 | Expression::Repeat(_)
1319 | Expression::Lpad(_)
1320 | Expression::Rpad(_)
1321 | Expression::ConcatWs(_) => TypeFamily::String,
1322 Expression::Abs(_)
1323 | Expression::Round(_)
1324 | Expression::Floor(_)
1325 | Expression::Ceil(_)
1326 | Expression::Power(_)
1327 | Expression::Sqrt(_)
1328 | Expression::Cbrt(_)
1329 | Expression::Ln(_)
1330 | Expression::Log(_)
1331 | Expression::Exp(_) => TypeFamily::Numeric,
1332 Expression::DateAdd(_) | Expression::DateSub(_) | Expression::ToDate(_) => TypeFamily::Date,
1333 Expression::ToTimestamp(_) => TypeFamily::Timestamp,
1334 Expression::DateDiff(_) | Expression::Extract(_) => TypeFamily::Integer,
1335 Expression::CurrentDate(_) => TypeFamily::Date,
1336 Expression::CurrentTime(_) => TypeFamily::Time,
1337 Expression::CurrentTimestamp(_) | Expression::CurrentTimestampLTZ(_) => {
1338 TypeFamily::Timestamp
1339 }
1340 Expression::Interval(_) => TypeFamily::Interval,
1341 _ => TypeFamily::Unknown,
1342 }
1343}
1344
1345fn are_comparable(left: TypeFamily, right: TypeFamily) -> bool {
1346 if left == TypeFamily::Unknown || right == TypeFamily::Unknown {
1347 return true;
1348 }
1349 if left == right {
1350 return true;
1351 }
1352 if left.is_numeric() && right.is_numeric() {
1353 return true;
1354 }
1355 if left.is_temporal() && right.is_temporal() {
1356 return true;
1357 }
1358 false
1359}
1360
1361fn check_function_argument(
1362 errors: &mut Vec<ValidationError>,
1363 strict: bool,
1364 function_name: &str,
1365 arg_index: usize,
1366 family: TypeFamily,
1367 expected: &str,
1368 valid: bool,
1369) {
1370 if family == TypeFamily::Unknown || valid {
1371 return;
1372 }
1373
1374 errors.push(type_issue(
1375 strict,
1376 validation_codes::E_INVALID_FUNCTION_ARGUMENT_TYPE,
1377 validation_codes::W_FUNCTION_ARGUMENT_COERCION,
1378 format!(
1379 "Function '{}' argument {} expects {}, found {}",
1380 function_name,
1381 arg_index + 1,
1382 expected,
1383 type_family_name(family)
1384 ),
1385 ));
1386}
1387
1388fn function_dispatch_name(name: &str) -> String {
1389 let upper = name
1390 .rsplit('.')
1391 .next()
1392 .unwrap_or(name)
1393 .trim()
1394 .to_uppercase();
1395 lower(canonical_typed_function_name_upper(&upper))
1396}
1397
1398fn function_base_name(name: &str) -> &str {
1399 name.rsplit('.').next().unwrap_or(name).trim()
1400}
1401
1402fn check_generic_function(
1403 function: &Function,
1404 schema_map: &HashMap<String, TableSchemaEntry>,
1405 context: &TypeCheckContext,
1406 strict: bool,
1407 errors: &mut Vec<ValidationError>,
1408) {
1409 let name = function_dispatch_name(&function.name);
1410
1411 let arg_family = |index: usize| -> Option<TypeFamily> {
1412 function
1413 .args
1414 .get(index)
1415 .map(|arg| infer_expression_type_family(arg, schema_map, context))
1416 };
1417
1418 match name.as_str() {
1419 "abs" | "sqrt" | "cbrt" | "ln" | "exp" => {
1420 if let Some(family) = arg_family(0) {
1421 check_function_argument(
1422 errors,
1423 strict,
1424 &name,
1425 0,
1426 family,
1427 "a numeric argument",
1428 family.is_numeric(),
1429 );
1430 }
1431 }
1432 "round" | "floor" | "ceil" | "ceiling" => {
1433 if let Some(family) = arg_family(0) {
1434 check_function_argument(
1435 errors,
1436 strict,
1437 &name,
1438 0,
1439 family,
1440 "a numeric argument",
1441 family.is_numeric(),
1442 );
1443 }
1444 if let Some(family) = arg_family(1) {
1445 check_function_argument(
1446 errors,
1447 strict,
1448 &name,
1449 1,
1450 family,
1451 "a numeric argument",
1452 family.is_numeric(),
1453 );
1454 }
1455 }
1456 "power" | "pow" => {
1457 for i in [0_usize, 1_usize] {
1458 if let Some(family) = arg_family(i) {
1459 check_function_argument(
1460 errors,
1461 strict,
1462 &name,
1463 i,
1464 family,
1465 "a numeric argument",
1466 family.is_numeric(),
1467 );
1468 }
1469 }
1470 }
1471 "length" | "char_length" | "character_length" => {
1472 if let Some(family) = arg_family(0) {
1473 check_function_argument(
1474 errors,
1475 strict,
1476 &name,
1477 0,
1478 family,
1479 "a string or binary argument",
1480 is_string_or_binary(family),
1481 );
1482 }
1483 }
1484 "upper" | "lower" | "trim" | "ltrim" | "rtrim" | "reverse" => {
1485 if let Some(family) = arg_family(0) {
1486 check_function_argument(
1487 errors,
1488 strict,
1489 &name,
1490 0,
1491 family,
1492 "a string argument",
1493 is_string_like(family),
1494 );
1495 }
1496 }
1497 "substring" | "substr" => {
1498 if let Some(family) = arg_family(0) {
1499 check_function_argument(
1500 errors,
1501 strict,
1502 &name,
1503 0,
1504 family,
1505 "a string argument",
1506 is_string_like(family),
1507 );
1508 }
1509 if let Some(family) = arg_family(1) {
1510 check_function_argument(
1511 errors,
1512 strict,
1513 &name,
1514 1,
1515 family,
1516 "a numeric argument",
1517 family.is_numeric(),
1518 );
1519 }
1520 if let Some(family) = arg_family(2) {
1521 check_function_argument(
1522 errors,
1523 strict,
1524 &name,
1525 2,
1526 family,
1527 "a numeric argument",
1528 family.is_numeric(),
1529 );
1530 }
1531 }
1532 "replace" => {
1533 for i in [0_usize, 1_usize, 2_usize] {
1534 if let Some(family) = arg_family(i) {
1535 check_function_argument(
1536 errors,
1537 strict,
1538 &name,
1539 i,
1540 family,
1541 "a string argument",
1542 is_string_like(family),
1543 );
1544 }
1545 }
1546 }
1547 "left" | "right" | "repeat" | "lpad" | "rpad" => {
1548 if let Some(family) = arg_family(0) {
1549 check_function_argument(
1550 errors,
1551 strict,
1552 &name,
1553 0,
1554 family,
1555 "a string argument",
1556 is_string_like(family),
1557 );
1558 }
1559 if let Some(family) = arg_family(1) {
1560 check_function_argument(
1561 errors,
1562 strict,
1563 &name,
1564 1,
1565 family,
1566 "a numeric argument",
1567 family.is_numeric(),
1568 );
1569 }
1570 if (name == "lpad" || name == "rpad") && function.args.len() > 2 {
1571 if let Some(family) = arg_family(2) {
1572 check_function_argument(
1573 errors,
1574 strict,
1575 &name,
1576 2,
1577 family,
1578 "a string argument",
1579 is_string_like(family),
1580 );
1581 }
1582 }
1583 }
1584 _ => {}
1585 }
1586}
1587
1588fn check_function_catalog(
1589 function: &Function,
1590 dialect: DialectType,
1591 function_catalog: Option<&dyn FunctionCatalog>,
1592 strict: bool,
1593 errors: &mut Vec<ValidationError>,
1594) {
1595 let Some(catalog) = function_catalog else {
1596 return;
1597 };
1598
1599 let raw_name = function_base_name(&function.name);
1600 let normalized_name = function_dispatch_name(&function.name);
1601 let arity = function.args.len();
1602 let Some(signatures) = catalog.lookup(dialect, raw_name, &normalized_name) else {
1603 errors.push(if strict {
1604 ValidationError::error(
1605 format!(
1606 "Unknown function '{}' for dialect {:?}",
1607 function.name, dialect
1608 ),
1609 validation_codes::E_UNKNOWN_FUNCTION,
1610 )
1611 } else {
1612 ValidationError::warning(
1613 format!(
1614 "Unknown function '{}' for dialect {:?}",
1615 function.name, dialect
1616 ),
1617 validation_codes::E_UNKNOWN_FUNCTION,
1618 )
1619 });
1620 return;
1621 };
1622
1623 if signatures.iter().any(|sig| sig.matches_arity(arity)) {
1624 return;
1625 }
1626
1627 let expected = signatures
1628 .iter()
1629 .map(|sig| sig.describe_arity())
1630 .collect::<Vec<_>>()
1631 .join(", ");
1632 errors.push(if strict {
1633 ValidationError::error(
1634 format!(
1635 "Invalid arity for function '{}': got {}, expected {}",
1636 function.name, arity, expected
1637 ),
1638 validation_codes::E_INVALID_FUNCTION_ARITY,
1639 )
1640 } else {
1641 ValidationError::warning(
1642 format!(
1643 "Invalid arity for function '{}': got {}, expected {}",
1644 function.name, arity, expected
1645 ),
1646 validation_codes::E_INVALID_FUNCTION_ARITY,
1647 )
1648 });
1649}
1650
1651#[derive(Debug, Clone, PartialEq, Eq, Hash)]
1652struct DeclaredRelationship {
1653 source_table: String,
1654 source_column: String,
1655 target_table: String,
1656 target_column: String,
1657}
1658
1659fn build_declared_relationships(
1660 schema: &ValidationSchema,
1661 schema_map: &HashMap<String, TableSchemaEntry>,
1662) -> Vec<DeclaredRelationship> {
1663 let mut relationships = HashSet::new();
1664
1665 for table in &schema.tables {
1666 let Some(source_key) =
1667 resolve_reference_table_key(&table.name, table.schema.as_deref(), None, schema_map)
1668 else {
1669 continue;
1670 };
1671
1672 for column in &table.columns {
1673 let Some(reference) = &column.references else {
1674 continue;
1675 };
1676 let Some(target_key) = resolve_reference_table_key(
1677 &reference.table,
1678 reference.schema.as_deref(),
1679 table.schema.as_deref(),
1680 schema_map,
1681 ) else {
1682 continue;
1683 };
1684
1685 relationships.insert(DeclaredRelationship {
1686 source_table: source_key.clone(),
1687 source_column: lower(&column.name),
1688 target_table: target_key,
1689 target_column: lower(&reference.column),
1690 });
1691 }
1692
1693 for foreign_key in &table.foreign_keys {
1694 if foreign_key.columns.len() != foreign_key.references.columns.len() {
1695 continue;
1696 }
1697 let Some(target_key) = resolve_reference_table_key(
1698 &foreign_key.references.table,
1699 foreign_key.references.schema.as_deref(),
1700 table.schema.as_deref(),
1701 schema_map,
1702 ) else {
1703 continue;
1704 };
1705
1706 for (source_col, target_col) in foreign_key
1707 .columns
1708 .iter()
1709 .zip(foreign_key.references.columns.iter())
1710 {
1711 relationships.insert(DeclaredRelationship {
1712 source_table: source_key.clone(),
1713 source_column: lower(source_col),
1714 target_table: target_key.clone(),
1715 target_column: lower(target_col),
1716 });
1717 }
1718 }
1719 }
1720
1721 relationships.into_iter().collect()
1722}
1723
1724fn resolve_column_binding(
1725 column: &Column,
1726 schema_map: &HashMap<String, TableSchemaEntry>,
1727 context: &TypeCheckContext,
1728 resolver: &mut Resolver<'_>,
1729) -> Option<(String, String)> {
1730 let column_name = lower(&column.name.name);
1731 if column_name.is_empty() {
1732 return None;
1733 }
1734
1735 if let Some(table) = &column.table {
1736 let mut table_key = lower(&table.name);
1737 if let Some(mapped) = context.table_aliases.get(&table_key) {
1738 table_key = mapped.clone();
1739 }
1740 if schema_map.contains_key(&table_key) {
1741 return Some((table_key, column_name));
1742 }
1743 return None;
1744 }
1745
1746 if let Some(resolved_source) = resolver.get_table(&column_name) {
1747 let mut table_key = lower(&resolved_source);
1748 if let Some(mapped) = context.table_aliases.get(&table_key) {
1749 table_key = mapped.clone();
1750 }
1751 if schema_map.contains_key(&table_key) {
1752 return Some((table_key, column_name));
1753 }
1754 }
1755
1756 let candidates: Vec<String> = context
1757 .referenced_tables
1758 .iter()
1759 .filter_map(|table_name| {
1760 schema_map
1761 .get(table_name)
1762 .filter(|entry| entry.columns.contains_key(&column_name))
1763 .map(|_| table_name.clone())
1764 })
1765 .collect();
1766 if candidates.len() == 1 {
1767 return Some((candidates[0].clone(), column_name));
1768 }
1769 None
1770}
1771
1772fn extract_join_equality_pairs(
1773 expr: &Expression,
1774 schema_map: &HashMap<String, TableSchemaEntry>,
1775 context: &TypeCheckContext,
1776 resolver: &mut Resolver<'_>,
1777 pairs: &mut Vec<((String, String), (String, String))>,
1778) {
1779 match expr {
1780 Expression::And(op) => {
1781 extract_join_equality_pairs(&op.left, schema_map, context, resolver, pairs);
1782 extract_join_equality_pairs(&op.right, schema_map, context, resolver, pairs);
1783 }
1784 Expression::Paren(paren) => {
1785 extract_join_equality_pairs(&paren.this, schema_map, context, resolver, pairs);
1786 }
1787 Expression::Eq(op) => {
1788 let (Expression::Column(left_col), Expression::Column(right_col)) =
1789 (&op.left, &op.right)
1790 else {
1791 return;
1792 };
1793 let Some(left) = resolve_column_binding(left_col, schema_map, context, resolver) else {
1794 return;
1795 };
1796 let Some(right) = resolve_column_binding(right_col, schema_map, context, resolver)
1797 else {
1798 return;
1799 };
1800 pairs.push((left, right));
1801 }
1802 _ => {}
1803 }
1804}
1805
1806fn relationship_matches_pair(
1807 relationship: &DeclaredRelationship,
1808 left_table: &str,
1809 left_column: &str,
1810 right_table: &str,
1811 right_column: &str,
1812) -> bool {
1813 (relationship.source_table == left_table
1814 && relationship.source_column == left_column
1815 && relationship.target_table == right_table
1816 && relationship.target_column == right_column)
1817 || (relationship.source_table == right_table
1818 && relationship.source_column == right_column
1819 && relationship.target_table == left_table
1820 && relationship.target_column == left_column)
1821}
1822
1823fn resolved_table_key_from_expr(
1824 expr: &Expression,
1825 schema_map: &HashMap<String, TableSchemaEntry>,
1826) -> Option<String> {
1827 match expr {
1828 Expression::Table(table) => resolve_table_schema_entry(table, schema_map).map(|(k, _)| k),
1829 Expression::Alias(alias) => resolved_table_key_from_expr(&alias.this, schema_map),
1830 _ => None,
1831 }
1832}
1833
1834fn select_from_table_keys(
1835 select: &crate::expressions::Select,
1836 schema_map: &HashMap<String, TableSchemaEntry>,
1837) -> HashSet<String> {
1838 let mut keys = HashSet::new();
1839 if let Some(from_clause) = &select.from {
1840 for expr in &from_clause.expressions {
1841 if let Some(key) = resolved_table_key_from_expr(expr, schema_map) {
1842 keys.insert(key);
1843 }
1844 }
1845 }
1846 keys
1847}
1848
1849fn is_natural_or_implied_join(kind: JoinKind) -> bool {
1850 matches!(
1851 kind,
1852 JoinKind::Natural
1853 | JoinKind::NaturalLeft
1854 | JoinKind::NaturalRight
1855 | JoinKind::NaturalFull
1856 | JoinKind::CrossApply
1857 | JoinKind::OuterApply
1858 | JoinKind::AsOf
1859 | JoinKind::AsOfLeft
1860 | JoinKind::AsOfRight
1861 | JoinKind::Lateral
1862 | JoinKind::LeftLateral
1863 )
1864}
1865
1866fn check_query_reference_quality(
1867 stmt: &Expression,
1868 schema_map: &HashMap<String, TableSchemaEntry>,
1869 resolver_schema: &MappingSchema,
1870 relationships: &[DeclaredRelationship],
1871) -> Vec<ValidationError> {
1872 let mut errors = Vec::new();
1873
1874 for node in stmt.dfs() {
1875 let Expression::Select(select) = node else {
1876 continue;
1877 };
1878
1879 let select_expr = Expression::Select(select.clone());
1880 let context = collect_type_check_context(&select_expr, schema_map);
1881 let scope = build_scope(&select_expr);
1882 let mut resolver = Resolver::new(&scope, resolver_schema, true);
1883
1884 let mut cumulative_left_tables = select_from_table_keys(select, schema_map);
1885
1886 for join in &select.joins {
1887 let right_table_key = resolved_table_key_from_expr(&join.this, schema_map);
1888 let has_explicit_condition = join.on.is_some() || !join.using.is_empty();
1889 let cartesian_like_kind = matches!(
1890 join.kind,
1891 JoinKind::Cross
1892 | JoinKind::Implicit
1893 | JoinKind::Array
1894 | JoinKind::LeftArray
1895 | JoinKind::Paste
1896 );
1897
1898 if right_table_key.is_some()
1899 && (cartesian_like_kind
1900 || (!has_explicit_condition && !is_natural_or_implied_join(join.kind)))
1901 {
1902 errors.push(ValidationError::warning(
1903 "Potential cartesian join: JOIN without ON/USING condition",
1904 validation_codes::W_CARTESIAN_JOIN,
1905 ));
1906 }
1907
1908 if let (Some(on_expr), Some(right_key)) = (&join.on, right_table_key.clone()) {
1909 if join.using.is_empty() {
1910 let mut eq_pairs = Vec::new();
1911 extract_join_equality_pairs(
1912 on_expr,
1913 schema_map,
1914 &context,
1915 &mut resolver,
1916 &mut eq_pairs,
1917 );
1918
1919 let relevant_relationships: Vec<&DeclaredRelationship> = relationships
1920 .iter()
1921 .filter(|rel| {
1922 cumulative_left_tables.contains(&rel.source_table)
1923 && rel.target_table == right_key
1924 || (cumulative_left_tables.contains(&rel.target_table)
1925 && rel.source_table == right_key)
1926 })
1927 .collect();
1928
1929 if !relevant_relationships.is_empty() {
1930 let uses_declared_fk = eq_pairs.iter().any(|((lt, lc), (rt, rc))| {
1931 relevant_relationships
1932 .iter()
1933 .any(|rel| relationship_matches_pair(rel, lt, lc, rt, rc))
1934 });
1935 if !uses_declared_fk {
1936 errors.push(ValidationError::warning(
1937 "JOIN predicate does not use declared foreign-key relationship columns",
1938 validation_codes::W_JOIN_NOT_USING_DECLARED_REFERENCE,
1939 ));
1940 }
1941 }
1942 }
1943 }
1944
1945 if let Some(right_key) = right_table_key {
1946 cumulative_left_tables.insert(right_key);
1947 }
1948 }
1949 }
1950
1951 errors
1952}
1953
1954fn are_setop_compatible(left: TypeFamily, right: TypeFamily) -> bool {
1955 if left == TypeFamily::Unknown || right == TypeFamily::Unknown {
1956 return true;
1957 }
1958 if left == right {
1959 return true;
1960 }
1961 if left.is_numeric() && right.is_numeric() {
1962 return true;
1963 }
1964 if left.is_temporal() && right.is_temporal() {
1965 return true;
1966 }
1967 false
1968}
1969
1970fn merged_setop_family(left: TypeFamily, right: TypeFamily) -> TypeFamily {
1971 if left == TypeFamily::Unknown {
1972 return right;
1973 }
1974 if right == TypeFamily::Unknown {
1975 return left;
1976 }
1977 if left == right {
1978 return left;
1979 }
1980 if left.is_numeric() && right.is_numeric() {
1981 if left == TypeFamily::Numeric || right == TypeFamily::Numeric {
1982 return TypeFamily::Numeric;
1983 }
1984 return TypeFamily::Integer;
1985 }
1986 if left.is_temporal() && right.is_temporal() {
1987 if left == TypeFamily::Timestamp || right == TypeFamily::Timestamp {
1988 return TypeFamily::Timestamp;
1989 }
1990 if left == TypeFamily::Date || right == TypeFamily::Date {
1991 return TypeFamily::Date;
1992 }
1993 return TypeFamily::Time;
1994 }
1995 TypeFamily::Unknown
1996}
1997
1998fn are_assignment_compatible(target: TypeFamily, source: TypeFamily) -> bool {
1999 if target == TypeFamily::Unknown || source == TypeFamily::Unknown {
2000 return true;
2001 }
2002 if target == source {
2003 return true;
2004 }
2005
2006 match target {
2007 TypeFamily::Boolean => source == TypeFamily::Boolean,
2008 TypeFamily::Integer | TypeFamily::Numeric => source.is_numeric(),
2009 TypeFamily::Date | TypeFamily::Time | TypeFamily::Timestamp | TypeFamily::Interval => {
2010 source.is_temporal()
2011 }
2012 TypeFamily::String => true,
2013 TypeFamily::Binary => matches!(source, TypeFamily::Binary | TypeFamily::String),
2014 TypeFamily::Json => matches!(source, TypeFamily::Json | TypeFamily::String),
2015 TypeFamily::Uuid => matches!(source, TypeFamily::Uuid | TypeFamily::String),
2016 TypeFamily::Array => source == TypeFamily::Array,
2017 TypeFamily::Map => source == TypeFamily::Map,
2018 TypeFamily::Struct => source == TypeFamily::Struct,
2019 TypeFamily::Unknown => true,
2020 }
2021}
2022
2023fn projection_families(
2024 query: &Expression,
2025 schema_map: &HashMap<String, TableSchemaEntry>,
2026 dialect: DialectType,
2027) -> Option<Vec<TypeFamily>> {
2028 let pair = match query {
2029 Expression::Union(query) => Some((&query.left, &query.right)),
2030 Expression::Intersect(query) => Some((&query.left, &query.right)),
2031 Expression::Except(query) => Some((&query.left, &query.right)),
2032 _ => None,
2033 };
2034 if let Some((left, right)) = pair {
2035 let left = projection_families(left, schema_map, dialect)?;
2036 let right = projection_families(right, schema_map, dialect)?;
2037 return match crate::set_operation::set_operation_layout(query, Some(dialect)).ok()? {
2038 Some(layout) => Some(
2039 layout
2040 .outputs
2041 .iter()
2042 .map(|output| {
2043 merged_setop_family(
2044 output
2045 .left_ordinal
2046 .and_then(|i| left.get(i).copied())
2047 .unwrap_or(TypeFamily::Unknown),
2048 output
2049 .right_ordinal
2050 .and_then(|i| right.get(i).copied())
2051 .unwrap_or(TypeFamily::Unknown),
2052 )
2053 })
2054 .collect(),
2055 ),
2056 None if left.len() == right.len() => Some(
2057 left.into_iter()
2058 .zip(right)
2059 .map(|(l, r)| merged_setop_family(l, r))
2060 .collect(),
2061 ),
2062 _ => None,
2063 };
2064 }
2065 match query {
2066 Expression::Select(select) => {
2067 if select
2068 .expressions
2069 .iter()
2070 .any(|e| matches!(e, Expression::Star(_) | Expression::BracedWildcard(_)))
2071 {
2072 return None;
2073 }
2074 let context = collect_type_check_context(query, schema_map);
2075 Some(
2076 select
2077 .expressions
2078 .iter()
2079 .map(|e| infer_expression_type_family(e, schema_map, &context))
2080 .collect(),
2081 )
2082 }
2083 Expression::Subquery(query) => projection_families(&query.this, schema_map, dialect),
2084 Expression::Paren(query) => projection_families(&query.this, schema_map, dialect),
2085 Expression::Values(values) => Some(
2086 values
2087 .expressions
2088 .first()?
2089 .expressions
2090 .iter()
2091 .map(|e| infer_expression_type_family(e, schema_map, &TypeCheckContext::default()))
2092 .collect(),
2093 ),
2094 _ => None,
2095 }
2096}
2097
2098fn check_set_operation_compatibility(
2099 query: &Expression,
2100 dialect: DialectType,
2101 op_name: &str,
2102 left_expr: &Expression,
2103 right_expr: &Expression,
2104 schema_map: &HashMap<String, TableSchemaEntry>,
2105 strict: bool,
2106 errors: &mut Vec<ValidationError>,
2107) {
2108 let Some(mut left_projection) = projection_families(left_expr, schema_map, dialect) else {
2109 return;
2110 };
2111 let Some(mut right_projection) = projection_families(right_expr, schema_map, dialect) else {
2112 return;
2113 };
2114
2115 match crate::set_operation::set_operation_layout(query, Some(dialect)) {
2116 Ok(Some(layout)) => {
2117 let left = left_projection;
2118 let right = right_projection;
2119 left_projection = layout
2120 .outputs
2121 .iter()
2122 .map(|output| {
2123 output
2124 .left_ordinal
2125 .and_then(|i| left.get(i).copied())
2126 .unwrap_or(TypeFamily::Unknown)
2127 })
2128 .collect();
2129 right_projection = layout
2130 .outputs
2131 .iter()
2132 .map(|output| {
2133 output
2134 .right_ordinal
2135 .and_then(|i| right.get(i).copied())
2136 .unwrap_or(TypeFamily::Unknown)
2137 })
2138 .collect();
2139 }
2140 Err(error) => {
2141 if !error.is_indeterminate() {
2142 errors.push(type_issue(
2143 strict,
2144 validation_codes::E_SETOP_ARITY_MISMATCH,
2145 validation_codes::W_SETOP_IMPLICIT_COERCION,
2146 error.to_string(),
2147 ));
2148 }
2149 return;
2150 }
2151 Ok(None) => {}
2152 }
2153 if left_projection.len() != right_projection.len() {
2154 errors.push(type_issue(
2155 strict,
2156 validation_codes::E_SETOP_ARITY_MISMATCH,
2157 validation_codes::W_SETOP_IMPLICIT_COERCION,
2158 format!(
2159 "{} operands return different column counts: left {}, right {}",
2160 op_name,
2161 left_projection.len(),
2162 right_projection.len()
2163 ),
2164 ));
2165 return;
2166 }
2167
2168 for (idx, (left, right)) in left_projection
2169 .into_iter()
2170 .zip(right_projection)
2171 .enumerate()
2172 {
2173 if !are_setop_compatible(left, right) {
2174 errors.push(type_issue(
2175 strict,
2176 validation_codes::E_SETOP_TYPE_MISMATCH,
2177 validation_codes::W_SETOP_IMPLICIT_COERCION,
2178 format!(
2179 "{} column {} has incompatible types: {} vs {}",
2180 op_name,
2181 idx + 1,
2182 type_family_name(left),
2183 type_family_name(right)
2184 ),
2185 ));
2186 }
2187 }
2188}
2189
2190fn check_insert_assignments(
2191 dialect: DialectType,
2192 stmt: &Expression,
2193 insert: &Insert,
2194 schema_map: &HashMap<String, TableSchemaEntry>,
2195 strict: bool,
2196 errors: &mut Vec<ValidationError>,
2197) {
2198 let Some((target_table_key, table_schema)) =
2199 resolve_table_schema_entry(&insert.table, schema_map)
2200 else {
2201 return;
2202 };
2203
2204 let mut target_columns = Vec::new();
2205 if insert.columns.is_empty() {
2206 target_columns.extend(table_schema.column_order.iter().cloned());
2207 } else {
2208 for column in &insert.columns {
2209 let col_name = lower(&column.name);
2210 if table_schema.columns.contains_key(&col_name) {
2211 target_columns.push(col_name);
2212 } else {
2213 errors.push(if strict {
2214 ValidationError::error(
2215 format!(
2216 "Unknown column '{}' in table '{}'",
2217 column.name, target_table_key
2218 ),
2219 validation_codes::E_UNKNOWN_COLUMN,
2220 )
2221 } else {
2222 ValidationError::warning(
2223 format!(
2224 "Unknown column '{}' in table '{}'",
2225 column.name, target_table_key
2226 ),
2227 validation_codes::E_UNKNOWN_COLUMN,
2228 )
2229 });
2230 }
2231 }
2232 }
2233
2234 if target_columns.is_empty() {
2235 return;
2236 }
2237
2238 let context = collect_type_check_context(stmt, schema_map);
2239
2240 if !insert.default_values {
2241 for (row_idx, row) in insert.values.iter().enumerate() {
2242 if row.len() != target_columns.len() {
2243 errors.push(type_issue(
2244 strict,
2245 validation_codes::E_INVALID_ASSIGNMENT_TYPE,
2246 validation_codes::W_IMPLICIT_CAST_ASSIGNMENT,
2247 format!(
2248 "INSERT row {} has {} values but target has {} columns",
2249 row_idx + 1,
2250 row.len(),
2251 target_columns.len()
2252 ),
2253 ));
2254 continue;
2255 }
2256
2257 for (value, target_column) in row.iter().zip(target_columns.iter()) {
2258 let Some(target_family) = table_schema.columns.get(target_column).copied() else {
2259 continue;
2260 };
2261 let source_family = infer_expression_type_family(value, schema_map, &context);
2262 if !are_assignment_compatible(target_family, source_family) {
2263 errors.push(type_issue(
2264 strict,
2265 validation_codes::E_INVALID_ASSIGNMENT_TYPE,
2266 validation_codes::W_IMPLICIT_CAST_ASSIGNMENT,
2267 format!(
2268 "INSERT assignment type mismatch for '{}.{}': expected {}, found {}",
2269 target_table_key,
2270 target_column,
2271 type_family_name(target_family),
2272 type_family_name(source_family)
2273 ),
2274 ));
2275 }
2276 }
2277 }
2278 }
2279
2280 if let Some(query) = &insert.query {
2281 if insert.by_name {
2283 return;
2284 }
2285
2286 let Some(source_projection) = projection_families(query, schema_map, dialect) else {
2287 return;
2288 };
2289
2290 if source_projection.len() != target_columns.len() {
2291 errors.push(type_issue(
2292 strict,
2293 validation_codes::E_INVALID_ASSIGNMENT_TYPE,
2294 validation_codes::W_IMPLICIT_CAST_ASSIGNMENT,
2295 format!(
2296 "INSERT source query has {} columns but target has {} columns",
2297 source_projection.len(),
2298 target_columns.len()
2299 ),
2300 ));
2301 return;
2302 }
2303
2304 for (source_family, target_column) in
2305 source_projection.into_iter().zip(target_columns.iter())
2306 {
2307 let Some(target_family) = table_schema.columns.get(target_column).copied() else {
2308 continue;
2309 };
2310 if !are_assignment_compatible(target_family, source_family) {
2311 errors.push(type_issue(
2312 strict,
2313 validation_codes::E_INVALID_ASSIGNMENT_TYPE,
2314 validation_codes::W_IMPLICIT_CAST_ASSIGNMENT,
2315 format!(
2316 "INSERT assignment type mismatch for '{}.{}': expected {}, found {}",
2317 target_table_key,
2318 target_column,
2319 type_family_name(target_family),
2320 type_family_name(source_family)
2321 ),
2322 ));
2323 }
2324 }
2325 }
2326}
2327
2328fn check_update_assignments(
2329 stmt: &Expression,
2330 update: &Update,
2331 schema_map: &HashMap<String, TableSchemaEntry>,
2332 strict: bool,
2333 errors: &mut Vec<ValidationError>,
2334) {
2335 let Some((target_table_key, table_schema)) =
2336 resolve_table_schema_entry(&update.table, schema_map)
2337 else {
2338 return;
2339 };
2340
2341 let context = collect_type_check_context(stmt, schema_map);
2342
2343 for (column, value) in &update.set {
2344 let col_name = lower(&column.name);
2345 let Some(target_family) = table_schema.columns.get(&col_name).copied() else {
2346 errors.push(if strict {
2347 ValidationError::error(
2348 format!(
2349 "Unknown column '{}' in table '{}'",
2350 column.name, target_table_key
2351 ),
2352 validation_codes::E_UNKNOWN_COLUMN,
2353 )
2354 } else {
2355 ValidationError::warning(
2356 format!(
2357 "Unknown column '{}' in table '{}'",
2358 column.name, target_table_key
2359 ),
2360 validation_codes::E_UNKNOWN_COLUMN,
2361 )
2362 });
2363 continue;
2364 };
2365
2366 let source_family = infer_expression_type_family(value, schema_map, &context);
2367 if !are_assignment_compatible(target_family, source_family) {
2368 errors.push(type_issue(
2369 strict,
2370 validation_codes::E_INVALID_ASSIGNMENT_TYPE,
2371 validation_codes::W_IMPLICIT_CAST_ASSIGNMENT,
2372 format!(
2373 "UPDATE assignment type mismatch for '{}.{}': expected {}, found {}",
2374 target_table_key,
2375 col_name,
2376 type_family_name(target_family),
2377 type_family_name(source_family)
2378 ),
2379 ));
2380 }
2381 }
2382}
2383
2384fn check_types(
2385 stmt: &Expression,
2386 dialect: DialectType,
2387 schema_map: &HashMap<String, TableSchemaEntry>,
2388 function_catalog: Option<&dyn FunctionCatalog>,
2389 strict: bool,
2390) -> Vec<ValidationError> {
2391 let mut errors = Vec::new();
2392 let context = collect_type_check_context(stmt, schema_map);
2393
2394 for node in stmt.dfs() {
2395 for (clause, predicate) in crate::binding::predicates(node) {
2396 let family = infer_expression_type_family(predicate, schema_map, &context);
2397 if !predicate_compatible(family, dialect) {
2398 errors.push(type_issue(
2399 strict,
2400 validation_codes::E_INVALID_PREDICATE_TYPE,
2401 validation_codes::W_PREDICATE_NULLABILITY,
2402 format!(
2403 "{clause} expects a boolean predicate, found {}",
2404 type_family_name(family)
2405 ),
2406 ));
2407 }
2408 }
2409 match node {
2410 Expression::Insert(insert) => {
2411 check_insert_assignments(dialect, stmt, insert, schema_map, strict, &mut errors);
2412 }
2413 Expression::Update(update) => {
2414 check_update_assignments(stmt, update, schema_map, strict, &mut errors);
2415 }
2416 Expression::Union(union) => {
2417 check_set_operation_compatibility(
2418 node,
2419 dialect,
2420 "UNION",
2421 &union.left,
2422 &union.right,
2423 schema_map,
2424 strict,
2425 &mut errors,
2426 );
2427 }
2428 Expression::Intersect(intersect) => {
2429 check_set_operation_compatibility(
2430 node,
2431 dialect,
2432 "INTERSECT",
2433 &intersect.left,
2434 &intersect.right,
2435 schema_map,
2436 strict,
2437 &mut errors,
2438 );
2439 }
2440 Expression::Except(except) => {
2441 check_set_operation_compatibility(
2442 node,
2443 dialect,
2444 "EXCEPT",
2445 &except.left,
2446 &except.right,
2447 schema_map,
2448 strict,
2449 &mut errors,
2450 );
2451 }
2452 Expression::And(op) | Expression::Or(op) => {
2453 for (side, expr) in [("left", &op.left), ("right", &op.right)] {
2454 let family = infer_expression_type_family(expr, schema_map, &context);
2455 if !predicate_compatible(family, dialect) {
2456 errors.push(type_issue(
2457 strict,
2458 validation_codes::E_INVALID_PREDICATE_TYPE,
2459 validation_codes::W_PREDICATE_NULLABILITY,
2460 format!(
2461 "Logical {} operand expects boolean, found {}",
2462 side,
2463 type_family_name(family)
2464 ),
2465 ));
2466 }
2467 }
2468 }
2469 Expression::Not(unary) => {
2470 let family = infer_expression_type_family(&unary.this, schema_map, &context);
2471 if !predicate_compatible(family, dialect) {
2472 errors.push(type_issue(
2473 strict,
2474 validation_codes::E_INVALID_PREDICATE_TYPE,
2475 validation_codes::W_PREDICATE_NULLABILITY,
2476 format!("NOT expects boolean, found {}", type_family_name(family)),
2477 ));
2478 }
2479 }
2480 Expression::Eq(op)
2481 | Expression::Neq(op)
2482 | Expression::Lt(op)
2483 | Expression::Lte(op)
2484 | Expression::Gt(op)
2485 | Expression::Gte(op) => {
2486 let left = infer_expression_type_family(&op.left, schema_map, &context);
2487 let right = infer_expression_type_family(&op.right, schema_map, &context);
2488 if !are_comparable(left, right) {
2489 errors.push(type_issue(
2490 strict,
2491 validation_codes::E_INCOMPATIBLE_COMPARISON_TYPES,
2492 validation_codes::W_IMPLICIT_CAST_COMPARISON,
2493 format!(
2494 "Incompatible comparison between {} and {}",
2495 type_family_name(left),
2496 type_family_name(right)
2497 ),
2498 ));
2499 }
2500 }
2501 Expression::Like(op) | Expression::ILike(op) => {
2502 let left = infer_expression_type_family(&op.left, schema_map, &context);
2503 let right = infer_expression_type_family(&op.right, schema_map, &context);
2504 if left != TypeFamily::Unknown
2505 && right != TypeFamily::Unknown
2506 && (!is_string_like(left) || !is_string_like(right))
2507 {
2508 errors.push(type_issue(
2509 strict,
2510 validation_codes::E_INCOMPATIBLE_COMPARISON_TYPES,
2511 validation_codes::W_IMPLICIT_CAST_COMPARISON,
2512 format!(
2513 "LIKE/ILIKE expects string operands, found {} and {}",
2514 type_family_name(left),
2515 type_family_name(right)
2516 ),
2517 ));
2518 }
2519 }
2520 Expression::Between(between) => {
2521 let this_family = infer_expression_type_family(&between.this, schema_map, &context);
2522 let low_family = infer_expression_type_family(&between.low, schema_map, &context);
2523 let high_family = infer_expression_type_family(&between.high, schema_map, &context);
2524
2525 if !are_comparable(this_family, low_family)
2526 || !are_comparable(this_family, high_family)
2527 {
2528 errors.push(type_issue(
2529 strict,
2530 validation_codes::E_INCOMPATIBLE_COMPARISON_TYPES,
2531 validation_codes::W_IMPLICIT_CAST_COMPARISON,
2532 format!(
2533 "BETWEEN bounds are incompatible with {} (found {} and {})",
2534 type_family_name(this_family),
2535 type_family_name(low_family),
2536 type_family_name(high_family)
2537 ),
2538 ));
2539 }
2540 }
2541 Expression::In(in_expr) => {
2542 let this_family = infer_expression_type_family(&in_expr.this, schema_map, &context);
2543 for value in &in_expr.expressions {
2544 let item_family = infer_expression_type_family(value, schema_map, &context);
2545 if !are_comparable(this_family, item_family) {
2546 errors.push(type_issue(
2547 strict,
2548 validation_codes::E_INCOMPATIBLE_COMPARISON_TYPES,
2549 validation_codes::W_IMPLICIT_CAST_COMPARISON,
2550 format!(
2551 "IN item type {} is incompatible with {}",
2552 type_family_name(item_family),
2553 type_family_name(this_family)
2554 ),
2555 ));
2556 break;
2557 }
2558 }
2559 }
2560 Expression::Add(op)
2561 | Expression::Sub(op)
2562 | Expression::Mul(op)
2563 | Expression::Div(op)
2564 | Expression::Mod(op) => {
2565 let left = infer_expression_type_family(&op.left, schema_map, &context);
2566 let right = infer_expression_type_family(&op.right, schema_map, &context);
2567
2568 if left == TypeFamily::Unknown || right == TypeFamily::Unknown {
2569 continue;
2570 }
2571
2572 let temporal_ok = matches!(node, Expression::Add(_) | Expression::Sub(_))
2573 && ((left.is_temporal() && right.is_numeric())
2574 || (right.is_temporal() && left.is_numeric())
2575 || (matches!(node, Expression::Sub(_))
2576 && left.is_temporal()
2577 && right.is_temporal()));
2578
2579 if !(left.is_numeric() && right.is_numeric()) && !temporal_ok {
2580 errors.push(type_issue(
2581 strict,
2582 validation_codes::E_INVALID_ARITHMETIC_TYPE,
2583 validation_codes::W_IMPLICIT_CAST_ARITHMETIC,
2584 format!(
2585 "Arithmetic operation expects numeric-compatible operands, found {} and {}",
2586 type_family_name(left),
2587 type_family_name(right)
2588 ),
2589 ));
2590 }
2591 }
2592 Expression::Function(function) => {
2593 check_function_catalog(function, dialect, function_catalog, strict, &mut errors);
2594 check_generic_function(function, schema_map, &context, strict, &mut errors);
2595 }
2596 Expression::Upper(func)
2597 | Expression::Lower(func)
2598 | Expression::LTrim(func)
2599 | Expression::RTrim(func)
2600 | Expression::Reverse(func) => {
2601 let family = infer_expression_type_family(&func.this, schema_map, &context);
2602 check_function_argument(
2603 &mut errors,
2604 strict,
2605 "string_function",
2606 0,
2607 family,
2608 "a string argument",
2609 is_string_like(family),
2610 );
2611 }
2612 Expression::Length(func) => {
2613 let family = infer_expression_type_family(&func.this, schema_map, &context);
2614 check_function_argument(
2615 &mut errors,
2616 strict,
2617 "length",
2618 0,
2619 family,
2620 "a string or binary argument",
2621 is_string_or_binary(family),
2622 );
2623 }
2624 Expression::Trim(func) => {
2625 let this_family = infer_expression_type_family(&func.this, schema_map, &context);
2626 check_function_argument(
2627 &mut errors,
2628 strict,
2629 "trim",
2630 0,
2631 this_family,
2632 "a string argument",
2633 is_string_like(this_family),
2634 );
2635 if let Some(chars) = &func.characters {
2636 let chars_family = infer_expression_type_family(chars, schema_map, &context);
2637 check_function_argument(
2638 &mut errors,
2639 strict,
2640 "trim",
2641 1,
2642 chars_family,
2643 "a string argument",
2644 is_string_like(chars_family),
2645 );
2646 }
2647 }
2648 Expression::Substring(func) => {
2649 let this_family = infer_expression_type_family(&func.this, schema_map, &context);
2650 check_function_argument(
2651 &mut errors,
2652 strict,
2653 "substring",
2654 0,
2655 this_family,
2656 "a string argument",
2657 is_string_like(this_family),
2658 );
2659
2660 let start_family = infer_expression_type_family(&func.start, schema_map, &context);
2661 check_function_argument(
2662 &mut errors,
2663 strict,
2664 "substring",
2665 1,
2666 start_family,
2667 "a numeric argument",
2668 start_family.is_numeric(),
2669 );
2670 if let Some(length) = &func.length {
2671 let length_family = infer_expression_type_family(length, schema_map, &context);
2672 check_function_argument(
2673 &mut errors,
2674 strict,
2675 "substring",
2676 2,
2677 length_family,
2678 "a numeric argument",
2679 length_family.is_numeric(),
2680 );
2681 }
2682 }
2683 Expression::Replace(func) => {
2684 for (arg, idx) in [
2685 (&func.this, 0_usize),
2686 (&func.old, 1_usize),
2687 (&func.new, 2_usize),
2688 ] {
2689 let family = infer_expression_type_family(arg, schema_map, &context);
2690 check_function_argument(
2691 &mut errors,
2692 strict,
2693 "replace",
2694 idx,
2695 family,
2696 "a string argument",
2697 is_string_like(family),
2698 );
2699 }
2700 }
2701 Expression::Left(func) | Expression::Right(func) => {
2702 let this_family = infer_expression_type_family(&func.this, schema_map, &context);
2703 check_function_argument(
2704 &mut errors,
2705 strict,
2706 "left_right",
2707 0,
2708 this_family,
2709 "a string argument",
2710 is_string_like(this_family),
2711 );
2712 let length_family =
2713 infer_expression_type_family(&func.length, schema_map, &context);
2714 check_function_argument(
2715 &mut errors,
2716 strict,
2717 "left_right",
2718 1,
2719 length_family,
2720 "a numeric argument",
2721 length_family.is_numeric(),
2722 );
2723 }
2724 Expression::Repeat(func) => {
2725 let this_family = infer_expression_type_family(&func.this, schema_map, &context);
2726 check_function_argument(
2727 &mut errors,
2728 strict,
2729 "repeat",
2730 0,
2731 this_family,
2732 "a string argument",
2733 is_string_like(this_family),
2734 );
2735 let times_family = infer_expression_type_family(&func.times, schema_map, &context);
2736 check_function_argument(
2737 &mut errors,
2738 strict,
2739 "repeat",
2740 1,
2741 times_family,
2742 "a numeric argument",
2743 times_family.is_numeric(),
2744 );
2745 }
2746 Expression::Lpad(func) | Expression::Rpad(func) => {
2747 let this_family = infer_expression_type_family(&func.this, schema_map, &context);
2748 check_function_argument(
2749 &mut errors,
2750 strict,
2751 "pad",
2752 0,
2753 this_family,
2754 "a string argument",
2755 is_string_like(this_family),
2756 );
2757 let length_family =
2758 infer_expression_type_family(&func.length, schema_map, &context);
2759 check_function_argument(
2760 &mut errors,
2761 strict,
2762 "pad",
2763 1,
2764 length_family,
2765 "a numeric argument",
2766 length_family.is_numeric(),
2767 );
2768 if let Some(fill) = &func.fill {
2769 let fill_family = infer_expression_type_family(fill, schema_map, &context);
2770 check_function_argument(
2771 &mut errors,
2772 strict,
2773 "pad",
2774 2,
2775 fill_family,
2776 "a string argument",
2777 is_string_like(fill_family),
2778 );
2779 }
2780 }
2781 Expression::Abs(func)
2782 | Expression::Sqrt(func)
2783 | Expression::Cbrt(func)
2784 | Expression::Ln(func)
2785 | Expression::Exp(func) => {
2786 let family = infer_expression_type_family(&func.this, schema_map, &context);
2787 check_function_argument(
2788 &mut errors,
2789 strict,
2790 "numeric_function",
2791 0,
2792 family,
2793 "a numeric argument",
2794 family.is_numeric(),
2795 );
2796 }
2797 Expression::Round(func) => {
2798 let this_family = infer_expression_type_family(&func.this, schema_map, &context);
2799 check_function_argument(
2800 &mut errors,
2801 strict,
2802 "round",
2803 0,
2804 this_family,
2805 "a numeric argument",
2806 this_family.is_numeric(),
2807 );
2808 if let Some(decimals) = &func.decimals {
2809 let decimals_family =
2810 infer_expression_type_family(decimals, schema_map, &context);
2811 check_function_argument(
2812 &mut errors,
2813 strict,
2814 "round",
2815 1,
2816 decimals_family,
2817 "a numeric argument",
2818 decimals_family.is_numeric(),
2819 );
2820 }
2821 }
2822 Expression::Floor(func) => {
2823 let this_family = infer_expression_type_family(&func.this, schema_map, &context);
2824 check_function_argument(
2825 &mut errors,
2826 strict,
2827 "floor",
2828 0,
2829 this_family,
2830 "a numeric argument",
2831 this_family.is_numeric(),
2832 );
2833 if let Some(scale) = &func.scale {
2834 let scale_family = infer_expression_type_family(scale, schema_map, &context);
2835 check_function_argument(
2836 &mut errors,
2837 strict,
2838 "floor",
2839 1,
2840 scale_family,
2841 "a numeric argument",
2842 scale_family.is_numeric(),
2843 );
2844 }
2845 }
2846 Expression::Ceil(func) => {
2847 let this_family = infer_expression_type_family(&func.this, schema_map, &context);
2848 check_function_argument(
2849 &mut errors,
2850 strict,
2851 "ceil",
2852 0,
2853 this_family,
2854 "a numeric argument",
2855 this_family.is_numeric(),
2856 );
2857 if let Some(decimals) = &func.decimals {
2858 let decimals_family =
2859 infer_expression_type_family(decimals, schema_map, &context);
2860 check_function_argument(
2861 &mut errors,
2862 strict,
2863 "ceil",
2864 1,
2865 decimals_family,
2866 "a numeric argument",
2867 decimals_family.is_numeric(),
2868 );
2869 }
2870 }
2871 Expression::Power(func) => {
2872 let left_family = infer_expression_type_family(&func.this, schema_map, &context);
2873 check_function_argument(
2874 &mut errors,
2875 strict,
2876 "power",
2877 0,
2878 left_family,
2879 "a numeric argument",
2880 left_family.is_numeric(),
2881 );
2882 let right_family =
2883 infer_expression_type_family(&func.expression, schema_map, &context);
2884 check_function_argument(
2885 &mut errors,
2886 strict,
2887 "power",
2888 1,
2889 right_family,
2890 "a numeric argument",
2891 right_family.is_numeric(),
2892 );
2893 }
2894 Expression::Log(func) => {
2895 let this_family = infer_expression_type_family(&func.this, schema_map, &context);
2896 check_function_argument(
2897 &mut errors,
2898 strict,
2899 "log",
2900 0,
2901 this_family,
2902 "a numeric argument",
2903 this_family.is_numeric(),
2904 );
2905 if let Some(base) = &func.base {
2906 let base_family = infer_expression_type_family(base, schema_map, &context);
2907 check_function_argument(
2908 &mut errors,
2909 strict,
2910 "log",
2911 1,
2912 base_family,
2913 "a numeric argument",
2914 base_family.is_numeric(),
2915 );
2916 }
2917 }
2918 _ => {}
2919 }
2920 }
2921
2922 errors
2923}
2924
2925fn predicate_compatible(family: TypeFamily, dialect: DialectType) -> bool {
2926 matches!(family, TypeFamily::Unknown | TypeFamily::Boolean)
2927 || (matches!(
2928 dialect,
2929 DialectType::MySQL | DialectType::SQLite | DialectType::DuckDB
2930 ) && family.is_numeric())
2931}
2932
2933mod semantics;
2934pub(crate) use semantics::check_semantics;
2935
2936fn resolve_scope_source_name(scope: &crate::scope::Scope, name: &str) -> Option<String> {
2937 scope
2938 .sources
2939 .get_key_value(name)
2940 .map(|(k, _)| k.clone())
2941 .or_else(|| {
2942 scope
2943 .sources
2944 .keys()
2945 .find(|source| source.eq_ignore_ascii_case(name))
2946 .cloned()
2947 })
2948}
2949
2950fn source_has_column(
2951 scope: &crate::scope::Scope,
2952 source: &str,
2953 columns: &[String],
2954 column: &crate::expressions::Identifier,
2955 dialect: DialectType,
2956) -> bool {
2957 let strategy = get_normalization_strategy(Some(dialect));
2958 let expected = normalize_identifier(column.clone(), strategy).name;
2959 columns.iter().any(|name| {
2960 if name == "*" {
2961 return true;
2962 }
2963 let identifier = scope
2964 .sources
2965 .get(source)
2966 .and_then(|source| source_output_identifier(&source.expression, name))
2967 .cloned()
2968 .unwrap_or_else(|| crate::binding::schema_identifier(name));
2969 normalize_identifier(identifier, strategy).name == expected
2970 })
2971}
2972
2973fn source_display_name(scope: &crate::scope::Scope, source_name: &str) -> String {
2974 scope
2975 .sources
2976 .get(source_name)
2977 .map(|source| match &source.expression {
2978 Expression::Table(table) => lower(&table_ref_display_name(table)),
2979 _ => lower(source_name),
2980 })
2981 .unwrap_or_else(|| lower(source_name))
2982}
2983
2984fn reference_diagnostic(
2986 message: String,
2987 code: &str,
2988 strict: bool,
2989 span: Option<crate::tokens::Span>,
2990) -> ValidationError {
2991 let mut error = if strict {
2992 ValidationError::error(message, code)
2993 } else {
2994 ValidationError::warning(message, code)
2995 };
2996 if let Some(span) = span {
2997 error = error
2998 .with_location(span.line, span.column)
2999 .with_span(Some(span.start), Some(span.end));
3000 }
3001 error
3002}
3003
3004use crate::scope::{scope_query, selected_reference_scope as selected_validation_scope};
3005
3006type ProjectionAliasBindings = HashMap<(usize, usize), DataType>;
3010
3011fn projection_reference_key(column: &Column) -> Option<(usize, usize)> {
3012 if column.table.is_some() {
3013 return None;
3014 }
3015 column
3016 .name
3017 .span
3018 .or(column.span)
3019 .map(|span| (span.start, span.end))
3020}
3021
3022use crate::binding::bound_identifier as validation_bound_identifier;
3023
3024fn apply_projection_alias_bindings(
3027 expression: Expression,
3028 bindings: &ProjectionAliasBindings,
3029) -> Expression {
3030 if bindings.is_empty() {
3031 return expression;
3032 }
3033 enum Task {
3034 Visit(Expression),
3035 Finish(Expression, usize),
3036 }
3037 let mut pending = vec![Task::Visit(expression)];
3038 let mut results = Vec::new();
3039 while let Some(task) = pending.pop() {
3040 match task {
3041 Task::Visit(mut expression) => {
3042 if let Expression::Column(column) = &expression {
3043 if let Some(data_type) =
3044 projection_reference_key(column).and_then(|key| bindings.get(&key))
3045 {
3046 results.push(validation_bound_identifier(column.name.clone(), data_type));
3047 continue;
3048 }
3049 }
3050 let mut children = Vec::new();
3051 crate::ast_children::for_each_child_mut(&mut expression, |child| {
3052 children.push(std::mem::replace(
3053 child,
3054 Expression::Null(crate::expressions::Null),
3055 ));
3056 });
3057 pending.push(Task::Finish(expression, children.len()));
3058 pending.extend(children.into_iter().rev().map(Task::Visit));
3059 }
3060 Task::Finish(mut expression, count) => {
3061 let mut children = results.split_off(results.len() - count).into_iter();
3062 crate::ast_children::for_each_child_mut(&mut expression, |child| {
3063 *child = children.next().expect("alias binding child");
3064 });
3065 results.push(expression);
3066 }
3067 }
3068 }
3069 results.pop().expect("alias binding result")
3070}
3071
3072fn bind_scope_projection_aliases(
3073 scope: &mut crate::scope::Scope,
3074 ancestors: &[&crate::scope::Scope],
3075 resolver_schema: &MappingSchema,
3076 dialect: DialectType,
3077 check_types: bool,
3078 bindings: &mut ProjectionAliasBindings,
3079) {
3080 let Expression::Select(select) = &scope.expression else {
3081 return;
3082 };
3083 if !select
3084 .expressions
3085 .iter()
3086 .any(|expression| matches!(expression, Expression::Alias(_)))
3087 {
3088 return;
3089 }
3090
3091 let strategy = get_normalization_strategy(Some(dialect));
3095 let mut inputs = HashSet::new();
3096 let mut open = false;
3097 for source_scope in std::iter::once(&*scope).chain(ancestors.iter().copied()) {
3098 let mut resolver = Resolver::new(source_scope, resolver_schema, true);
3099 for source in source_scope.sources.keys() {
3100 let columns = resolver.get_source_columns(source).unwrap_or_default();
3101 open |= columns.is_empty() || columns.iter().any(|name| name == "*");
3102 let expression = &source_scope.sources[source].expression;
3103 for name in columns {
3104 let identifier = source_output_identifier(expression, &name)
3105 .cloned()
3106 .unwrap_or_else(|| crate::expressions::Identifier::new(name));
3107 inputs.insert(normalize_identifier(identifier, strategy).name);
3108 }
3109 }
3110 }
3111 if open {
3112 return;
3113 }
3114
3115 let mut ctes: Vec<_> = if check_types {
3118 scope
3119 .cte_sources
3120 .values()
3121 .filter_map(|source| match &source.expression {
3122 Expression::Cte(cte) => Some(cte.as_ref().clone()),
3123 _ => None,
3124 })
3125 .collect()
3126 } else {
3127 Vec::new()
3128 };
3129 ctes.sort_by_key(|cte| cte.alias.span.map(|span| span.start));
3130 let mut aliases = HashMap::<String, Option<DataType>>::new();
3131 let Expression::Select(select) = &mut scope.expression else {
3132 unreachable!()
3133 };
3134 let mut projections = std::mem::take(&mut select.expressions);
3137 let mut type_select = select.clone();
3138 if !ctes.is_empty() {
3139 type_select.with = Some(crate::expressions::With {
3140 ctes,
3141 recursive: false,
3142 leading_comments: Vec::new(),
3143 search: None,
3144 });
3145 }
3146 for projection in projections.iter_mut() {
3147 let mut references = ProjectionAliasBindings::new();
3148 for node in walk_in_scope(projection, false) {
3149 if !crate::binding::lateral_aliases(dialect) {
3150 break;
3151 }
3152 let Expression::Column(column) = node else {
3153 continue;
3154 };
3155 let Some(key) = projection_reference_key(column) else {
3156 continue;
3157 };
3158 let name = normalize_identifier(column.name.clone(), strategy).name;
3159 if inputs.contains(&name) {
3160 continue;
3161 }
3162 if let Some(Some(data_type)) = aliases.get(&name) {
3163 references.insert(key, data_type.clone());
3164 }
3165 }
3166 let original = std::mem::replace(projection, Expression::Null(crate::expressions::Null));
3167 *projection = apply_projection_alias_bindings(original, &references);
3168 bindings.extend(references);
3169 if let Expression::Alias(alias) = projection {
3170 let name = normalize_identifier(alias.alias.clone(), strategy).name;
3171 if aliases.contains_key(&name) {
3173 aliases.insert(name, None);
3174 continue;
3175 }
3176 if !check_types {
3177 aliases.insert(name, Some(DataType::Unknown));
3178 continue;
3179 }
3180 let mut typed = Expression::Select(type_select.clone());
3181 if let Expression::Select(query) = &mut typed {
3182 query.expressions.push(alias.this.clone());
3183 }
3184 typed = apply_projection_alias_bindings(typed, bindings);
3187 annotate_types(&mut typed, Some(resolver_schema), Some(dialect));
3188 let data_type = match typed {
3189 Expression::Select(query) => query.expressions[0]
3190 .inferred_type()
3191 .cloned()
3192 .unwrap_or(DataType::Unknown),
3193 _ => unreachable!(),
3194 };
3195 aliases.insert(name, Some(data_type));
3196 }
3197 }
3198 select.expressions = projections;
3199 use crate::binding::{clause_aliases, AliasClause};
3202 let mut clauses = Vec::new();
3203 if clause_aliases(dialect, AliasClause::Where) {
3204 if let Some(clause) = &mut select.where_clause {
3205 clauses.push(&mut clause.this);
3206 }
3207 }
3208 if clause_aliases(dialect, AliasClause::Group) {
3209 if let Some(clause) = &mut select.group_by {
3210 clauses.extend(clause.expressions.iter_mut());
3211 }
3212 }
3213 if clause_aliases(dialect, AliasClause::Having) {
3214 if let Some(clause) = &mut select.having {
3215 clauses.push(&mut clause.this);
3216 }
3217 }
3218 if clause_aliases(dialect, AliasClause::Qualify) {
3219 if let Some(clause) = &mut select.qualify {
3220 clauses.push(&mut clause.this);
3221 }
3222 }
3223 for clause in clauses {
3224 let mut references = ProjectionAliasBindings::new();
3225 for node in walk_in_scope(clause, false) {
3226 let Expression::Column(column) = node else {
3227 continue;
3228 };
3229 let Some(key) = projection_reference_key(column) else {
3230 continue;
3231 };
3232 let name = normalize_identifier(column.name.clone(), strategy).name;
3233 if !inputs.contains(&name) {
3234 if let Some(Some(data_type)) = aliases.get(&name) {
3235 references.insert(key, data_type.clone());
3236 }
3237 }
3238 }
3239 let original = std::mem::replace(clause, Expression::Null(crate::expressions::Null));
3240 *clause = apply_projection_alias_bindings(original, &references);
3241 bindings.extend(references);
3242 }
3243}
3244
3245fn source_output_identifier<'a>(
3248 expression: &'a Expression,
3249 name: &str,
3250) -> Option<&'a crate::expressions::Identifier> {
3251 let columns: &[crate::expressions::Identifier] = match expression {
3252 Expression::Cte(cte) => &cte.columns,
3253 Expression::Subquery(query) => &query.column_aliases,
3254 Expression::Alias(alias) => &alias.column_aliases,
3255 Expression::Table(table) => &table.column_aliases,
3256 Expression::Values(values) => &values.column_aliases,
3257 Expression::Paren(paren) => return source_output_identifier(&paren.this, name),
3258 _ => &[],
3259 };
3260 if !columns.is_empty() {
3261 return columns.iter().find(|column| column.name == name);
3262 }
3263 match scope_query(expression) {
3264 Expression::Values(values) => values
3265 .column_aliases
3266 .iter()
3267 .find(|column| column.name == name),
3268 Expression::Select(select) => select.expressions.iter().find_map(|projection| {
3269 let identifier = match projection {
3270 Expression::Alias(alias) => &alias.alias,
3271 Expression::Column(column) => &column.name,
3272 Expression::Identifier(identifier) => identifier,
3273 _ => return None,
3274 };
3275 (identifier.name == name).then_some(identifier)
3276 }),
3277 Expression::Union(query) => source_output_identifier(&query.left, name),
3278 Expression::Intersect(query) => source_output_identifier(&query.left, name),
3279 Expression::Except(query) => source_output_identifier(&query.left, name),
3280 _ => None,
3281 }
3282}
3283
3284use crate::binding::bind_lambdas as bind_validation_lambdas;
3290
3291#[derive(Clone, Copy)]
3292struct ReferenceValidationOptions {
3293 dialect: DialectType,
3294 strict: bool,
3295 check_references: bool,
3296 check_types: bool,
3297}
3298
3299#[derive(Clone, Copy, PartialEq, Eq)]
3300enum OutputNameResolution {
3301 InputOnly,
3302 OutputFirst,
3303 InputFirst,
3304}
3305
3306fn order_by_validation_columns(
3309 order_by: &crate::expressions::OrderBy,
3310 dialect: DialectType,
3311) -> Vec<(&Column, OutputNameResolution)> {
3312 let scalar_resolution = match dialect {
3316 DialectType::DuckDB => OutputNameResolution::InputFirst,
3319 DialectType::Snowflake | DialectType::BigQuery => OutputNameResolution::OutputFirst,
3320 _ => OutputNameResolution::InputOnly,
3321 };
3322 let mut columns = Vec::new();
3323 let mut pending: Vec<_> = order_by
3324 .expressions
3325 .iter()
3326 .rev()
3327 .map(|ordered| (&ordered.this, OutputNameResolution::OutputFirst))
3328 .collect();
3329 while let Some((node, resolution)) = pending.pop() {
3330 match node {
3331 Expression::Select(_)
3334 | Expression::Union(_)
3335 | Expression::Intersect(_)
3336 | Expression::Except(_)
3337 | Expression::Subquery(_)
3338 | Expression::Exists(_)
3339 | Expression::Cte(_) => continue,
3340 Expression::Column(column) => columns.push((column.as_ref(), resolution)),
3341 Expression::Paren(paren) => pending.push((&paren.this, resolution)),
3342 _ => {
3343 let input_only = resolution == OutputNameResolution::InputOnly
3344 || matches!(
3345 node,
3346 Expression::WindowFunction(_)
3347 | Expression::WithinGroup(_)
3348 | Expression::Filter(_)
3349 )
3350 || crate::traversal::is_aggregate(node);
3351 if input_only {
3352 columns.extend(walk_in_scope(node, false).filter_map(|node| match node {
3353 Expression::Column(column) => {
3354 Some((column.as_ref(), OutputNameResolution::InputOnly))
3355 }
3356 _ => None,
3357 }));
3358 } else {
3359 pending.extend(
3360 node.children()
3361 .into_iter()
3362 .rev()
3363 .map(|child| (child, scalar_resolution)),
3364 );
3365 }
3366 }
3367 }
3368 }
3369 columns
3370}
3371
3372fn validate_scope_columns(
3373 scope: &crate::scope::Scope,
3374 ancestors: &[&crate::scope::Scope],
3375 schema_map: &HashMap<String, TableSchemaEntry>,
3376 resolver_schema: &MappingSchema,
3377 options: ReferenceValidationOptions,
3378) -> Vec<ValidationError> {
3379 let ReferenceValidationOptions {
3380 dialect,
3381 strict,
3382 check_references,
3383 ..
3384 } = options;
3385 let mut errors = Vec::new();
3386 let Expression::Select(select) = &scope.expression else {
3387 return errors;
3388 };
3389
3390 let mut visible = crate::scope::Scope::new(scope.expression.clone());
3393 for source_scope in std::iter::once(scope).chain(ancestors.iter().copied()) {
3394 for (name, source) in &source_scope.sources {
3395 if resolve_scope_source_name(&visible, name).is_none() {
3396 visible.sources.insert(name.clone(), source.clone());
3397 }
3398 }
3399 }
3400 let mut normalized = select.clone();
3401 let mut normalizer = Resolver::new(&visible, resolver_schema, true);
3402 let _ = normalize_dotted_columns_in_scope(&mut normalized, &visible, &mut normalizer);
3403 let strategy = get_normalization_strategy(Some(dialect));
3404 let mut output_names = HashMap::<String, usize>::new();
3405 for projection in &normalized.expressions {
3406 let identifier = match projection {
3407 Expression::Star(_) => {
3411 output_names.clear();
3412 break;
3413 }
3414 Expression::Alias(alias) => &alias.alias,
3415 Expression::Column(column) if column.name.name != "*" => &column.name,
3416 Expression::Column(_) => {
3417 output_names.clear();
3418 break;
3419 }
3420 _ => continue,
3421 };
3422 let name = normalize_identifier(identifier.clone(), strategy).name;
3423 *output_names.entry(name).or_default() += 1;
3424 }
3425 let order_by = normalized.order_by.take();
3429 let expression = Expression::Select(normalized);
3430 let mut resolvers: Vec<_> = std::iter::once(scope)
3431 .chain(ancestors.iter().copied())
3432 .map(|scope| (scope, Resolver::new(scope, resolver_schema, true)))
3433 .collect();
3434 let using_columns: HashSet<String> = select
3435 .joins
3436 .iter()
3437 .flat_map(|join| join.using.iter().map(|id| lower(&id.name)))
3438 .collect();
3439
3440 let input_columns = walk_in_scope(&expression, false).filter_map(|node| match node {
3441 Expression::Column(column) => Some((column.as_ref(), OutputNameResolution::InputOnly)),
3442 _ => None,
3443 });
3444 let order_columns = order_by
3445 .as_ref()
3446 .into_iter()
3447 .flat_map(|order| order_by_validation_columns(order, dialect));
3448 for (column, resolution) in input_columns.chain(order_columns) {
3449 let output_matches =
3450 if resolution != OutputNameResolution::InputOnly && column.table.is_none() {
3451 let name = normalize_identifier(column.name.clone(), strategy).name;
3452 output_names.get(&name) == Some(&1)
3455 } else {
3456 false
3457 };
3458 if output_matches && resolution == OutputNameResolution::OutputFirst {
3459 continue;
3460 }
3461 let name = lower(&column.name.name);
3462 if name.is_empty() || name == "*" {
3463 continue;
3464 }
3465 let span = column.name.span.or(column.span);
3466 if let Some(qualifier) = &column.table {
3467 let resolved = resolvers.iter_mut().find_map(|(scope, resolver)| {
3468 resolve_scope_source_name(scope, &qualifier.name).map(|source| {
3469 (
3470 *scope,
3471 resolver.get_source_columns(&source).unwrap_or_default(),
3472 source,
3473 )
3474 })
3475 });
3476 if let Some((source_scope, columns, source)) = resolved {
3477 if !columns.is_empty()
3478 && !source_has_column(source_scope, &source, &columns, &column.name, dialect)
3479 {
3480 errors.push(reference_diagnostic(
3481 format!(
3482 "Unknown column '{}' in table '{}'",
3483 name,
3484 source_display_name(source_scope, &source)
3485 ),
3486 validation_codes::E_UNKNOWN_COLUMN,
3487 strict,
3488 span,
3489 ));
3490 }
3491 } else {
3492 errors.push(reference_diagnostic(
3493 format!(
3494 "Unknown table or alias '{}' referenced by column '{}'",
3495 qualifier.name, name
3496 ),
3497 validation_codes::E_UNRESOLVED_REFERENCE,
3498 strict,
3499 qualifier.span.or(column.span),
3500 ));
3501 }
3502 continue;
3503 }
3504
3505 let mut found = false;
3506 for (source_scope, resolver) in &mut resolvers {
3507 let mut matches = 0;
3508 let mut open = false;
3509 for source in source_scope.sources.keys() {
3510 let columns = resolver.get_source_columns(source).unwrap_or_default();
3511 open |= columns.is_empty() || columns.iter().any(|name| name == "*");
3512 matches += usize::from(
3514 !columns.iter().any(|name| name == "*")
3515 && source_has_column(source_scope, source, &columns, &column.name, dialect),
3516 );
3517 }
3518 if matches > 0 || open {
3519 if matches > 1 && check_references && !using_columns.contains(&name) {
3520 errors.push(reference_diagnostic(
3521 format!(
3522 "Ambiguous unqualified column '{}' found in {} referenced tables",
3523 name, matches
3524 ),
3525 if strict {
3526 validation_codes::E_AMBIGUOUS_COLUMN_REFERENCE
3527 } else {
3528 validation_codes::W_WEAK_REFERENCE_INTEGRITY
3529 },
3530 strict,
3531 span,
3532 ));
3533 }
3534 found = true;
3535 break;
3536 }
3537 }
3538 if !found {
3539 if output_matches && resolution == OutputNameResolution::InputFirst {
3540 continue;
3541 }
3542 if scope.sources.is_empty()
3546 && ancestors.is_empty()
3547 && (schema_map.is_empty()
3548 || schema_map
3549 .values()
3550 .any(|table| table.columns.contains_key(&name)))
3551 {
3552 continue;
3553 }
3554 let message = if scope.sources.len() == 1 {
3555 let source = scope.sources.keys().next().unwrap();
3556 format!(
3557 "Unknown column '{}' in table '{}'",
3558 name,
3559 source_display_name(scope, source)
3560 )
3561 } else {
3562 format!(
3563 "Unknown column '{}' (not found in any referenced table)",
3564 name
3565 )
3566 };
3567 errors.push(reference_diagnostic(
3568 message,
3569 validation_codes::E_UNKNOWN_COLUMN,
3570 strict,
3571 span,
3572 ));
3573 }
3574 }
3575 errors
3576}
3577
3578fn validate_scope_tree(
3579 scope: &crate::scope::Scope,
3580 ancestors: &[&crate::scope::Scope],
3581 schema_map: &HashMap<String, TableSchemaEntry>,
3582 resolver_schema: &MappingSchema,
3583 options: ReferenceValidationOptions,
3584 errors: &mut Vec<ValidationError>,
3585 bindings: &mut ProjectionAliasBindings,
3586) {
3587 let mut selected = selected_validation_scope(scope);
3588 let mut child_errors = Vec::new();
3591 for child in scope
3594 .cte_scopes
3595 .iter()
3596 .chain(&scope.derived_table_scopes)
3597 .chain(&scope.union_scopes)
3598 {
3599 let lateral = child.is_lateral.then(|| {
3603 let mut lateral = crate::scope::Scope::new(Expression::null());
3604 lateral.sources = child.lateral_sources.clone();
3605 lateral
3606 });
3607 let lateral_ancestors = lateral.as_ref().map(|lateral| {
3608 std::iter::once(lateral)
3609 .chain(ancestors.iter().copied())
3610 .collect::<Vec<_>>()
3611 });
3612 validate_scope_tree(
3613 child,
3614 lateral_ancestors.as_deref().unwrap_or(ancestors),
3615 schema_map,
3616 resolver_schema,
3617 options,
3618 &mut child_errors,
3619 bindings,
3620 );
3621 }
3622 let outer: Vec<_> = std::iter::once(&selected)
3623 .chain(ancestors.iter().copied())
3624 .collect();
3625 for child in scope.subquery_scopes.iter().chain(&scope.udtf_scopes) {
3626 validate_scope_tree(
3627 child,
3628 &outer,
3629 schema_map,
3630 resolver_schema,
3631 options,
3632 &mut child_errors,
3633 bindings,
3634 );
3635 }
3636 bind_scope_projection_aliases(
3637 &mut selected,
3638 ancestors,
3639 resolver_schema,
3640 options.dialect,
3641 options.check_types,
3642 bindings,
3643 );
3644 for node in walk_in_scope(&selected.expression, false) {
3645 let Expression::Table(table) = node else {
3646 continue;
3647 };
3648 let is_cte = table.schema.is_none()
3649 && table.catalog.is_none()
3650 && selected
3651 .cte_sources
3652 .keys()
3653 .any(|name| name.eq_ignore_ascii_case(&table.name.name));
3654 if !is_cte {
3655 validate_schema_table(table, schema_map, options.strict, errors);
3656 }
3657 }
3658 errors.extend(validate_scope_columns(
3659 &selected,
3660 ancestors,
3661 schema_map,
3662 resolver_schema,
3663 options,
3664 ));
3665 errors.extend(child_errors);
3666}
3667
3668fn validate_schema_table(
3669 table: &TableRef,
3670 schema_map: &HashMap<String, TableSchemaEntry>,
3671 strict: bool,
3672 errors: &mut Vec<ValidationError>,
3673) {
3674 if !table_ref_candidates(table)
3675 .iter()
3676 .any(|key| schema_map.contains_key(key))
3677 {
3678 errors.push(reference_diagnostic(
3679 format!("Unknown table '{}'", table_ref_display_name(table)),
3680 validation_codes::E_UNKNOWN_TABLE,
3681 strict,
3682 table.name.span,
3683 ));
3684 }
3685}
3686
3687fn validate_statement_with_schema(
3688 stmt: &Expression,
3689 schema_map: &HashMap<String, TableSchemaEntry>,
3690 resolver_schema: &MappingSchema,
3691 options: ReferenceValidationOptions,
3692 bindings: &mut ProjectionAliasBindings,
3693) -> Vec<ValidationError> {
3694 let mut errors = Vec::new();
3695 let mut pending = vec![stmt];
3697 while let Some(expression) = pending.pop() {
3698 if let Some(select) = crate::binding::dml_scope(expression) {
3699 let query = Expression::Select(Box::new(select));
3700 let scope = build_scope(&query);
3701 validate_scope_tree(
3702 &scope,
3703 &[],
3704 schema_map,
3705 resolver_schema,
3706 options,
3707 &mut errors,
3708 bindings,
3709 );
3710 let target_columns = match expression {
3711 Expression::Insert(insert) => {
3712 Some((&insert.table, insert.columns.iter().collect::<Vec<_>>()))
3713 }
3714 Expression::Update(update) => Some((
3715 &update.table,
3716 update.set.iter().map(|(column, _)| column).collect(),
3717 )),
3718 _ => None,
3719 };
3720 if let Some((table, columns)) = target_columns {
3721 if let Some((_, entry)) = resolve_table_schema_entry(table, schema_map) {
3722 for column in columns {
3723 if !entry.columns.is_empty()
3724 && !entry.columns.contains_key("*")
3725 && !entry.columns.contains_key(&lower(&column.name))
3726 {
3727 errors.push(reference_diagnostic(
3728 format!(
3729 "Unknown column '{}' in table '{}'",
3730 column.name, table.name.name
3731 ),
3732 validation_codes::E_UNKNOWN_COLUMN,
3733 options.strict,
3734 column.span,
3735 ));
3736 }
3737 }
3738 }
3739 }
3740 if let Expression::Insert(insert) = expression {
3741 for value in insert.values.iter().flatten() {
3743 for node in walk_in_scope(value, false) {
3744 if let Expression::Column(column) = node {
3745 errors.push(reference_diagnostic(
3746 format!("Unknown column '{}' in INSERT VALUES", column.name.name),
3747 validation_codes::E_UNKNOWN_COLUMN,
3748 options.strict,
3749 column.name.span.or(column.span),
3750 ));
3751 }
3752 }
3753 pending.extend(value.children().into_iter().filter(|child| {
3754 matches!(child, Expression::Subquery(_) | Expression::Select(_))
3755 }));
3756 }
3757 if let Some(query) = &insert.query {
3758 let source = crate::scope::build_scope_with_ctes(query, &scope.cte_sources);
3762 let outer = crate::scope::Scope::new(Expression::null());
3766 validate_scope_tree(
3767 &source,
3768 &[&outer],
3769 schema_map,
3770 resolver_schema,
3771 options,
3772 &mut errors,
3773 bindings,
3774 );
3775 }
3776 }
3777 if let Expression::Merge(merge) = expression {
3778 let target = match merge.this.as_ref() {
3779 Expression::Table(table) => Some(table.as_ref()),
3780 Expression::Alias(alias) => match &alias.this {
3781 Expression::Table(table) => Some(table.as_ref()),
3782 _ => None,
3783 },
3784 _ => None,
3785 };
3786 if let Some((table, (_, entry))) = target.and_then(|table| {
3787 resolve_table_schema_entry(table, schema_map).map(|entry| (table, entry))
3788 }) {
3789 if let Some(whens) = &merge.whens {
3790 for node in whens.dfs() {
3791 let Expression::When(when) = node else {
3792 continue;
3793 };
3794 let Expression::Tuple(action) = when.then.as_ref() else {
3795 continue;
3796 };
3797 let Some(Expression::Var(kind)) = action.expressions.first() else {
3798 continue;
3799 };
3800 let Some(Expression::Tuple(assignments)) = action.expressions.get(1)
3801 else {
3802 continue;
3803 };
3804 for assignment in &assignments.expressions {
3805 let target = if kind.this.eq_ignore_ascii_case("UPDATE") {
3806 if let Expression::Eq(eq) = assignment {
3807 &eq.left
3808 } else {
3809 continue;
3810 }
3811 } else if kind.this.eq_ignore_ascii_case("INSERT") {
3812 assignment
3813 } else {
3814 continue;
3815 };
3816 let identifier = match target {
3817 Expression::Identifier(id) => id,
3818 Expression::Column(column) => &column.name,
3819 _ => continue,
3820 };
3821 if !entry.columns.is_empty()
3822 && !entry.columns.contains_key("*")
3823 && !entry.columns.contains_key(&lower(&identifier.name))
3824 {
3825 errors.push(reference_diagnostic(
3826 format!(
3827 "Unknown column '{}' in table '{}'",
3828 identifier.name, table.name.name
3829 ),
3830 validation_codes::E_UNKNOWN_COLUMN,
3831 options.strict,
3832 identifier.span,
3833 ));
3834 }
3835 }
3836 }
3837 }
3838 }
3839 }
3840 continue;
3841 }
3842 if matches!(
3843 scope_query(expression),
3844 Expression::Select(_)
3845 | Expression::Union(_)
3846 | Expression::Intersect(_)
3847 | Expression::Except(_)
3848 ) {
3849 let scope = build_scope(expression);
3850 validate_scope_tree(
3851 &scope,
3852 &[],
3853 schema_map,
3854 resolver_schema,
3855 options,
3856 &mut errors,
3857 bindings,
3858 );
3859 } else {
3860 if let Expression::CreateTable(create) = expression {
3862 if let Some(query) = &create.as_select {
3863 pending.push(query);
3864 }
3865 if let Some(source) = &create.clone_source {
3866 validate_schema_table(source, schema_map, options.strict, &mut errors);
3867 }
3868 continue;
3869 }
3870 if let Expression::Table(table) = expression {
3871 validate_schema_table(table, schema_map, options.strict, &mut errors);
3872 }
3873 pending.extend(expression.children().into_iter().rev());
3874 }
3875 }
3876 errors
3877}
3878
3879pub fn validate_with_schema(
3881 sql: &str,
3882 dialect: DialectType,
3883 schema: &ValidationSchema,
3884 options: &SchemaValidationOptions,
3885) -> ValidationResult {
3886 let strict = options.strict.unwrap_or(schema.strict.unwrap_or(true));
3887
3888 let d = Dialect::get(dialect);
3890 let statements = match crate::parse_for_validation(
3891 sql,
3892 &d,
3893 &crate::ValidationOptions {
3894 complexity_guard: options.complexity_guard,
3895 strict_syntax: options.strict_syntax,
3896 semantic: options.semantic,
3897 },
3898 ) {
3899 Ok(exprs) => exprs,
3900 Err(result) => return result,
3901 };
3902
3903 let schema_map = build_schema_map(schema);
3904 let resolver_schema = mapping_schema_from_validation_schema_with_dialect(schema, dialect);
3905 let mut all_errors = Vec::new();
3906 let embedded_function_catalog = if options.check_types && options.function_catalog.is_none() {
3907 default_embedded_function_catalog()
3908 } else {
3909 None
3910 };
3911 let effective_function_catalog = options
3912 .function_catalog
3913 .as_deref()
3914 .or_else(|| embedded_function_catalog.as_deref());
3915 let declared_relationships = if options.check_references {
3916 build_declared_relationships(schema, &schema_map)
3917 } else {
3918 Vec::new()
3919 };
3920
3921 if options.check_references {
3922 all_errors.extend(check_reference_integrity(schema, &schema_map, strict));
3923 }
3924
3925 for mut statement in statements {
3926 if options.semantic {
3927 all_errors.extend(check_semantics(&statement, dialect, Some(schema)));
3928 }
3929 crate::binding::bind_dml_pseudoreferences(&mut statement);
3930 let statement = bind_validation_lambdas(statement, dialect);
3931 let mut bindings = ProjectionAliasBindings::new();
3932 all_errors.extend(validate_statement_with_schema(
3933 &statement,
3934 &schema_map,
3935 &resolver_schema,
3936 ReferenceValidationOptions {
3937 dialect,
3938 strict,
3939 check_references: options.check_references,
3940 check_types: options.check_types,
3941 },
3942 &mut bindings,
3943 ));
3944 let mut statement = apply_projection_alias_bindings(statement, &bindings);
3945 if options.check_types {
3946 if statement.dfs().any(|node| {
3949 matches!(
3950 node,
3951 Expression::Union(_) | Expression::Intersect(_) | Expression::Except(_)
3952 )
3953 }) && statement
3954 .dfs()
3955 .any(|node| matches!(node, Expression::Star(_)))
3956 {
3957 if let Ok(qualified) = crate::optimizer::qualify_schema_aware_expression(
3958 statement.clone(),
3959 &resolver_schema,
3960 Some(dialect),
3961 ) {
3962 statement = qualified;
3963 }
3964 }
3965 annotate_types(&mut statement, Some(&resolver_schema), Some(dialect));
3966 }
3967 let stmt = &statement;
3968 if options.check_types {
3969 all_errors.extend(check_types(
3970 stmt,
3971 dialect,
3972 &schema_map,
3973 effective_function_catalog,
3974 strict,
3975 ));
3976 }
3977 if options.check_references {
3978 all_errors.extend(check_query_reference_quality(
3979 stmt,
3980 &schema_map,
3981 &resolver_schema,
3982 &declared_relationships,
3983 ));
3984 }
3985 }
3986
3987 ValidationResult::with_errors(all_errors)
3988}
3989
3990#[cfg(test)]
3991mod tests;