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::Boolean => TypeFamily::Boolean,
756 DataType::TinyInt { .. }
757 | DataType::SmallInt { .. }
758 | DataType::Int { .. }
759 | DataType::BigInt { .. }
760 | DataType::Int128
761 | DataType::UInt8
762 | DataType::UInt16
763 | DataType::UInt32
764 | DataType::UInt64
765 | DataType::UInt128 => TypeFamily::Integer,
766 DataType::Float { .. } | DataType::Double { .. } | DataType::Decimal { .. } => {
767 TypeFamily::Numeric
768 }
769 DataType::Oracle { oracle_type } => match oracle_type {
770 OracleDataType::Number { .. }
771 | OracleDataType::BinaryFloat
772 | OracleDataType::BinaryDouble
773 | OracleDataType::Float { .. } => TypeFamily::Numeric,
774 OracleDataType::Character { .. }
775 | OracleDataType::Clob { .. }
776 | OracleDataType::Long { raw: false }
777 | OracleDataType::RowId => TypeFamily::String,
778 OracleDataType::Blob
779 | OracleDataType::Raw { .. }
780 | OracleDataType::Long { raw: true } => TypeFamily::Binary,
781 OracleDataType::Date => TypeFamily::Date,
782 OracleDataType::Timestamp { .. } => TypeFamily::Timestamp,
783 OracleDataType::IntervalYearToMonth { .. }
784 | OracleDataType::IntervalDayToSecond { .. } => TypeFamily::Interval,
785 },
786 DataType::Char { .. }
787 | DataType::VarChar { .. }
788 | DataType::String { .. }
789 | DataType::Text
790 | DataType::TextWithLength { .. }
791 | DataType::CharacterSet { .. } => TypeFamily::String,
792 DataType::Binary { .. } | DataType::VarBinary { .. } | DataType::Blob => TypeFamily::Binary,
793 DataType::Date => TypeFamily::Date,
794 DataType::Time { .. } => TypeFamily::Time,
795 DataType::Timestamp { .. } => TypeFamily::Timestamp,
796 DataType::Interval { .. } => TypeFamily::Interval,
797 DataType::Json | DataType::JsonB => TypeFamily::Json,
798 DataType::Uuid => TypeFamily::Uuid,
799 DataType::Array { .. } | DataType::List { .. } => TypeFamily::Array,
800 DataType::Map { .. } => TypeFamily::Map,
801 DataType::Struct { .. } | DataType::Object { .. } | DataType::Union { .. } => {
802 TypeFamily::Struct
803 }
804 DataType::Nullable { inner } => data_type_family(inner),
805 DataType::Custom { name } => canonical_type_family(name),
806 DataType::Unknown => TypeFamily::Unknown,
807 DataType::Bit { .. } | DataType::VarBit { .. } => TypeFamily::Binary,
808 DataType::Enum { .. } | DataType::Set { .. } => TypeFamily::String,
809 DataType::Vector { .. } => TypeFamily::Array,
810 DataType::Geometry { .. } | DataType::Geography { .. } => TypeFamily::Struct,
811 }
812}
813
814fn collect_type_check_context(
815 stmt: &Expression,
816 schema_map: &HashMap<String, TableSchemaEntry>,
817) -> TypeCheckContext {
818 fn add_table_to_context(
819 table: &TableRef,
820 schema_map: &HashMap<String, TableSchemaEntry>,
821 context: &mut TypeCheckContext,
822 ) {
823 let resolved_key = table_ref_candidates(table)
824 .into_iter()
825 .find(|k| schema_map.contains_key(k));
826
827 let Some(table_key) = resolved_key else {
828 return;
829 };
830
831 context.referenced_tables.insert(table_key.clone());
832 context
833 .table_aliases
834 .insert(lower(&table.name.name), table_key.clone());
835 if let Some(alias) = &table.alias {
836 context
837 .table_aliases
838 .insert(lower(&alias.name), table_key.clone());
839 }
840 }
841
842 let mut context = TypeCheckContext::default();
843 let cte_aliases: HashSet<_> = build_scope(stmt)
844 .cte_sources
845 .keys()
846 .map(|name| lower(name))
847 .collect();
848
849 for node in walk_in_scope(stmt, false) {
850 let Expression::Table(table) = node else {
851 continue;
852 };
853
854 if cte_aliases.contains(&lower(&table.name.name)) {
855 continue;
856 }
857
858 add_table_to_context(table, schema_map, &mut context);
859 }
860
861 match stmt {
864 Expression::Insert(insert) => {
865 add_table_to_context(&insert.table, schema_map, &mut context);
866 }
867 Expression::Update(update) => {
868 add_table_to_context(&update.table, schema_map, &mut context);
869 for table in &update.extra_tables {
870 add_table_to_context(table, schema_map, &mut context);
871 }
872 }
873 Expression::Delete(delete) => {
874 add_table_to_context(&delete.table, schema_map, &mut context);
875 for table in &delete.using {
876 add_table_to_context(table, schema_map, &mut context);
877 }
878 for table in &delete.tables {
879 add_table_to_context(table, schema_map, &mut context);
880 }
881 }
882 _ => {}
883 }
884
885 context
886}
887
888fn resolve_table_schema_entry<'a>(
889 table: &TableRef,
890 schema_map: &'a HashMap<String, TableSchemaEntry>,
891) -> Option<(String, &'a TableSchemaEntry)> {
892 let key = table_ref_candidates(table)
893 .into_iter()
894 .find(|k| schema_map.contains_key(k))?;
895 let entry = schema_map.get(&key)?;
896 Some((key, entry))
897}
898
899fn reference_issue(strict: bool, message: impl Into<String>) -> ValidationError {
900 if strict {
901 ValidationError::error(
902 message.into(),
903 validation_codes::E_INVALID_FOREIGN_KEY_REFERENCE,
904 )
905 } else {
906 ValidationError::warning(message.into(), validation_codes::W_WEAK_REFERENCE_INTEGRITY)
907 }
908}
909
910fn reference_table_candidates(
911 table_name: &str,
912 explicit_schema: Option<&str>,
913 source_schema: Option<&str>,
914) -> Vec<String> {
915 let mut candidates = Vec::new();
916 let raw = lower(table_name);
917
918 if let Some(schema) = explicit_schema {
919 candidates.push(format!("{}.{}", lower(schema), raw));
920 }
921
922 if raw.contains('.') {
923 candidates.push(raw.clone());
924 if let Some(last) = raw.rsplit('.').next() {
925 candidates.push(last.to_string());
926 }
927 } else {
928 if let Some(schema) = source_schema {
929 candidates.push(format!("{}.{}", lower(schema), raw));
930 }
931 candidates.push(raw);
932 }
933
934 let mut dedup = HashSet::new();
935 candidates
936 .into_iter()
937 .filter(|c| dedup.insert(c.clone()))
938 .collect()
939}
940
941fn resolve_reference_table_key(
942 table_name: &str,
943 explicit_schema: Option<&str>,
944 source_schema: Option<&str>,
945 schema_map: &HashMap<String, TableSchemaEntry>,
946) -> Option<String> {
947 reference_table_candidates(table_name, explicit_schema, source_schema)
948 .into_iter()
949 .find(|candidate| schema_map.contains_key(candidate))
950}
951
952fn key_types_compatible(source: TypeFamily, target: TypeFamily) -> bool {
953 if source == TypeFamily::Unknown || target == TypeFamily::Unknown {
954 return true;
955 }
956 if source == target {
957 return true;
958 }
959 if source.is_numeric() && target.is_numeric() {
960 return true;
961 }
962 if source.is_temporal() && target.is_temporal() {
963 return true;
964 }
965 false
966}
967
968fn table_key_hints(table: &SchemaTable) -> HashSet<String> {
969 let mut hints = HashSet::new();
970 for column in &table.columns {
971 if column.primary_key || column.unique {
972 hints.insert(lower(&column.name));
973 }
974 }
975 for key_col in &table.primary_key {
976 hints.insert(lower(key_col));
977 }
978 for group in &table.unique_keys {
979 if group.len() == 1 {
980 if let Some(col) = group.first() {
981 hints.insert(lower(col));
982 }
983 }
984 }
985 hints
986}
987
988fn check_reference_integrity(
989 schema: &ValidationSchema,
990 schema_map: &HashMap<String, TableSchemaEntry>,
991 strict: bool,
992) -> Vec<ValidationError> {
993 let mut errors = Vec::new();
994
995 let mut key_hints_lookup: HashMap<String, HashSet<String>> = HashMap::new();
996 for table in &schema.tables {
997 let simple = lower(&table.name);
998 key_hints_lookup.insert(simple, table_key_hints(table));
999 if let Some(schema_name) = &table.schema {
1000 let qualified = format!("{}.{}", lower(schema_name), lower(&table.name));
1001 key_hints_lookup.insert(qualified, table_key_hints(table));
1002 }
1003 }
1004
1005 for table in &schema.tables {
1006 let source_table_display = if let Some(schema_name) = &table.schema {
1007 format!("{}.{}", schema_name, table.name)
1008 } else {
1009 table.name.clone()
1010 };
1011 let source_schema = table.schema.as_deref();
1012 let source_columns: HashMap<String, TypeFamily> = table
1013 .columns
1014 .iter()
1015 .map(|col| (lower(&col.name), canonical_type_family(&col.data_type)))
1016 .collect();
1017
1018 for source_col in &table.columns {
1019 let Some(reference) = &source_col.references else {
1020 continue;
1021 };
1022 let source_type = canonical_type_family(&source_col.data_type);
1023
1024 let Some(target_key) = resolve_reference_table_key(
1025 &reference.table,
1026 reference.schema.as_deref(),
1027 source_schema,
1028 schema_map,
1029 ) else {
1030 errors.push(reference_issue(
1031 strict,
1032 format!(
1033 "Foreign key reference '{}.{}' points to unknown table '{}'",
1034 source_table_display, source_col.name, reference.table
1035 ),
1036 ));
1037 continue;
1038 };
1039
1040 let target_column = lower(&reference.column);
1041 let Some(target_entry) = schema_map.get(&target_key) else {
1042 errors.push(reference_issue(
1043 strict,
1044 format!(
1045 "Foreign key reference '{}.{}' points to unknown table '{}'",
1046 source_table_display, source_col.name, reference.table
1047 ),
1048 ));
1049 continue;
1050 };
1051
1052 let Some(target_type) = target_entry.columns.get(&target_column).copied() else {
1053 errors.push(reference_issue(
1054 strict,
1055 format!(
1056 "Foreign key reference '{}.{}' points to unknown column '{}.{}'",
1057 source_table_display, source_col.name, target_key, reference.column
1058 ),
1059 ));
1060 continue;
1061 };
1062
1063 if !key_types_compatible(source_type, target_type) {
1064 errors.push(reference_issue(
1065 strict,
1066 format!(
1067 "Foreign key type mismatch for '{}.{}' -> '{}.{}': {} vs {}",
1068 source_table_display,
1069 source_col.name,
1070 target_key,
1071 reference.column,
1072 type_family_name(source_type),
1073 type_family_name(target_type)
1074 ),
1075 ));
1076 }
1077
1078 if let Some(target_key_hints) = key_hints_lookup.get(&target_key) {
1079 if !target_key_hints.contains(&target_column) {
1080 errors.push(ValidationError::warning(
1081 format!(
1082 "Referenced column '{}.{}' is not marked as primary/unique key",
1083 target_key, reference.column
1084 ),
1085 validation_codes::W_WEAK_REFERENCE_INTEGRITY,
1086 ));
1087 }
1088 }
1089 }
1090
1091 for foreign_key in &table.foreign_keys {
1092 if foreign_key.columns.is_empty() || foreign_key.references.columns.is_empty() {
1093 errors.push(reference_issue(
1094 strict,
1095 format!(
1096 "Table-level foreign key on '{}' has empty source or target column list",
1097 source_table_display
1098 ),
1099 ));
1100 continue;
1101 }
1102 if foreign_key.columns.len() != foreign_key.references.columns.len() {
1103 errors.push(reference_issue(
1104 strict,
1105 format!(
1106 "Table-level foreign key on '{}' has {} source columns but {} target columns",
1107 source_table_display,
1108 foreign_key.columns.len(),
1109 foreign_key.references.columns.len()
1110 ),
1111 ));
1112 continue;
1113 }
1114
1115 let Some(target_key) = resolve_reference_table_key(
1116 &foreign_key.references.table,
1117 foreign_key.references.schema.as_deref(),
1118 source_schema,
1119 schema_map,
1120 ) else {
1121 errors.push(reference_issue(
1122 strict,
1123 format!(
1124 "Table-level foreign key on '{}' points to unknown table '{}'",
1125 source_table_display, foreign_key.references.table
1126 ),
1127 ));
1128 continue;
1129 };
1130
1131 let Some(target_entry) = schema_map.get(&target_key) else {
1132 errors.push(reference_issue(
1133 strict,
1134 format!(
1135 "Table-level foreign key on '{}' points to unknown table '{}'",
1136 source_table_display, foreign_key.references.table
1137 ),
1138 ));
1139 continue;
1140 };
1141
1142 for (source_col, target_col) in foreign_key
1143 .columns
1144 .iter()
1145 .zip(foreign_key.references.columns.iter())
1146 {
1147 let source_col_name = lower(source_col);
1148 let target_col_name = lower(target_col);
1149
1150 let Some(source_type) = source_columns.get(&source_col_name).copied() else {
1151 errors.push(reference_issue(
1152 strict,
1153 format!(
1154 "Table-level foreign key on '{}' references unknown source column '{}'",
1155 source_table_display, source_col
1156 ),
1157 ));
1158 continue;
1159 };
1160
1161 let Some(target_type) = target_entry.columns.get(&target_col_name).copied() else {
1162 errors.push(reference_issue(
1163 strict,
1164 format!(
1165 "Table-level foreign key on '{}' references unknown target column '{}.{}'",
1166 source_table_display, target_key, target_col
1167 ),
1168 ));
1169 continue;
1170 };
1171
1172 if !key_types_compatible(source_type, target_type) {
1173 errors.push(reference_issue(
1174 strict,
1175 format!(
1176 "Table-level foreign key type mismatch '{}.{}' -> '{}.{}': {} vs {}",
1177 source_table_display,
1178 source_col,
1179 target_key,
1180 target_col,
1181 type_family_name(source_type),
1182 type_family_name(target_type)
1183 ),
1184 ));
1185 }
1186
1187 if let Some(target_key_hints) = key_hints_lookup.get(&target_key) {
1188 if !target_key_hints.contains(&target_col_name) {
1189 errors.push(ValidationError::warning(
1190 format!(
1191 "Referenced column '{}.{}' is not marked as primary/unique key",
1192 target_key, target_col
1193 ),
1194 validation_codes::W_WEAK_REFERENCE_INTEGRITY,
1195 ));
1196 }
1197 }
1198 }
1199 }
1200 }
1201
1202 errors
1203}
1204
1205fn infer_expression_type_family(
1206 expr: &Expression,
1207 schema_map: &HashMap<String, TableSchemaEntry>,
1208 context: &TypeCheckContext,
1209) -> TypeFamily {
1210 if let Some(data_type) = expr.inferred_type() {
1213 return data_type_family(data_type);
1214 }
1215 infer_expression_type_family_fallback(expr, schema_map, context)
1216}
1217
1218fn infer_expression_type_family_fallback(
1219 expr: &Expression,
1220 schema_map: &HashMap<String, TableSchemaEntry>,
1221 context: &TypeCheckContext,
1222) -> TypeFamily {
1223 match expr {
1224 Expression::Literal(literal) => match literal.as_ref() {
1225 crate::expressions::Literal::Number(value) => {
1226 if value.contains('.') || value.contains('e') || value.contains('E') {
1227 TypeFamily::Numeric
1228 } else {
1229 TypeFamily::Integer
1230 }
1231 }
1232 crate::expressions::Literal::HexNumber(_) => TypeFamily::Integer,
1233 crate::expressions::Literal::Date(_) => TypeFamily::Date,
1234 crate::expressions::Literal::Time(_) => TypeFamily::Time,
1235 crate::expressions::Literal::Timestamp(_)
1236 | crate::expressions::Literal::Datetime(_) => TypeFamily::Timestamp,
1237 crate::expressions::Literal::HexString(_)
1238 | crate::expressions::Literal::BitString(_)
1239 | crate::expressions::Literal::ByteString(_) => TypeFamily::Binary,
1240 _ => TypeFamily::String,
1241 },
1242 Expression::Boolean(_) => TypeFamily::Boolean,
1243 Expression::Null(_) => TypeFamily::Unknown,
1244 Expression::Column(_) => TypeFamily::Unknown,
1247 Expression::Cast(cast) | Expression::TryCast(cast) | Expression::SafeCast(cast) => {
1248 data_type_family(&cast.to)
1249 }
1250 Expression::Alias(alias) => {
1251 infer_expression_type_family_fallback(&alias.this, schema_map, context)
1252 }
1253 Expression::Neg(unary) => {
1254 infer_expression_type_family_fallback(&unary.this, schema_map, context)
1255 }
1256 Expression::Add(op) | Expression::Sub(op) | Expression::Mul(op) => {
1257 let left = infer_expression_type_family_fallback(&op.left, schema_map, context);
1258 let right = infer_expression_type_family_fallback(&op.right, schema_map, context);
1259 if left == TypeFamily::Unknown || right == TypeFamily::Unknown {
1260 TypeFamily::Unknown
1261 } else if left == TypeFamily::Integer && right == TypeFamily::Integer {
1262 TypeFamily::Integer
1263 } else if left.is_numeric() && right.is_numeric() {
1264 TypeFamily::Numeric
1265 } else if left.is_temporal() || right.is_temporal() {
1266 left
1267 } else {
1268 TypeFamily::Unknown
1269 }
1270 }
1271 Expression::Div(_) | Expression::Mod(_) => TypeFamily::Numeric,
1272 Expression::Concat(_) => TypeFamily::String,
1273 Expression::Eq(_)
1274 | Expression::Neq(_)
1275 | Expression::Lt(_)
1276 | Expression::Lte(_)
1277 | Expression::Gt(_)
1278 | Expression::Gte(_)
1279 | Expression::Like(_)
1280 | Expression::ILike(_)
1281 | Expression::And(_)
1282 | Expression::Or(_)
1283 | Expression::Not(_)
1284 | Expression::Between(_)
1285 | Expression::In(_)
1286 | Expression::IsNull(_)
1287 | Expression::IsTrue(_)
1288 | Expression::IsFalse(_)
1289 | Expression::Is(_) => TypeFamily::Boolean,
1290 Expression::Length(_) => TypeFamily::Integer,
1291 Expression::Upper(_)
1292 | Expression::Lower(_)
1293 | Expression::Trim(_)
1294 | Expression::LTrim(_)
1295 | Expression::RTrim(_)
1296 | Expression::Replace(_)
1297 | Expression::Substring(_)
1298 | Expression::Left(_)
1299 | Expression::Right(_)
1300 | Expression::Repeat(_)
1301 | Expression::Lpad(_)
1302 | Expression::Rpad(_)
1303 | Expression::ConcatWs(_) => TypeFamily::String,
1304 Expression::Abs(_)
1305 | Expression::Round(_)
1306 | Expression::Floor(_)
1307 | Expression::Ceil(_)
1308 | Expression::Power(_)
1309 | Expression::Sqrt(_)
1310 | Expression::Cbrt(_)
1311 | Expression::Ln(_)
1312 | Expression::Log(_)
1313 | Expression::Exp(_) => TypeFamily::Numeric,
1314 Expression::DateAdd(_) | Expression::DateSub(_) | Expression::ToDate(_) => TypeFamily::Date,
1315 Expression::ToTimestamp(_) => TypeFamily::Timestamp,
1316 Expression::DateDiff(_) | Expression::Extract(_) => TypeFamily::Integer,
1317 Expression::CurrentDate(_) => TypeFamily::Date,
1318 Expression::CurrentTime(_) => TypeFamily::Time,
1319 Expression::CurrentTimestamp(_) | Expression::CurrentTimestampLTZ(_) => {
1320 TypeFamily::Timestamp
1321 }
1322 Expression::Interval(_) => TypeFamily::Interval,
1323 _ => TypeFamily::Unknown,
1324 }
1325}
1326
1327fn are_comparable(left: TypeFamily, right: TypeFamily) -> bool {
1328 if left == TypeFamily::Unknown || right == TypeFamily::Unknown {
1329 return true;
1330 }
1331 if left == right {
1332 return true;
1333 }
1334 if left.is_numeric() && right.is_numeric() {
1335 return true;
1336 }
1337 if left.is_temporal() && right.is_temporal() {
1338 return true;
1339 }
1340 false
1341}
1342
1343fn check_function_argument(
1344 errors: &mut Vec<ValidationError>,
1345 strict: bool,
1346 function_name: &str,
1347 arg_index: usize,
1348 family: TypeFamily,
1349 expected: &str,
1350 valid: bool,
1351) {
1352 if family == TypeFamily::Unknown || valid {
1353 return;
1354 }
1355
1356 errors.push(type_issue(
1357 strict,
1358 validation_codes::E_INVALID_FUNCTION_ARGUMENT_TYPE,
1359 validation_codes::W_FUNCTION_ARGUMENT_COERCION,
1360 format!(
1361 "Function '{}' argument {} expects {}, found {}",
1362 function_name,
1363 arg_index + 1,
1364 expected,
1365 type_family_name(family)
1366 ),
1367 ));
1368}
1369
1370fn function_dispatch_name(name: &str) -> String {
1371 let upper = name
1372 .rsplit('.')
1373 .next()
1374 .unwrap_or(name)
1375 .trim()
1376 .to_uppercase();
1377 lower(canonical_typed_function_name_upper(&upper))
1378}
1379
1380fn function_base_name(name: &str) -> &str {
1381 name.rsplit('.').next().unwrap_or(name).trim()
1382}
1383
1384fn check_generic_function(
1385 function: &Function,
1386 schema_map: &HashMap<String, TableSchemaEntry>,
1387 context: &TypeCheckContext,
1388 strict: bool,
1389 errors: &mut Vec<ValidationError>,
1390) {
1391 let name = function_dispatch_name(&function.name);
1392
1393 let arg_family = |index: usize| -> Option<TypeFamily> {
1394 function
1395 .args
1396 .get(index)
1397 .map(|arg| infer_expression_type_family(arg, schema_map, context))
1398 };
1399
1400 match name.as_str() {
1401 "abs" | "sqrt" | "cbrt" | "ln" | "exp" => {
1402 if let Some(family) = arg_family(0) {
1403 check_function_argument(
1404 errors,
1405 strict,
1406 &name,
1407 0,
1408 family,
1409 "a numeric argument",
1410 family.is_numeric(),
1411 );
1412 }
1413 }
1414 "round" | "floor" | "ceil" | "ceiling" => {
1415 if let Some(family) = arg_family(0) {
1416 check_function_argument(
1417 errors,
1418 strict,
1419 &name,
1420 0,
1421 family,
1422 "a numeric argument",
1423 family.is_numeric(),
1424 );
1425 }
1426 if let Some(family) = arg_family(1) {
1427 check_function_argument(
1428 errors,
1429 strict,
1430 &name,
1431 1,
1432 family,
1433 "a numeric argument",
1434 family.is_numeric(),
1435 );
1436 }
1437 }
1438 "power" | "pow" => {
1439 for i in [0_usize, 1_usize] {
1440 if let Some(family) = arg_family(i) {
1441 check_function_argument(
1442 errors,
1443 strict,
1444 &name,
1445 i,
1446 family,
1447 "a numeric argument",
1448 family.is_numeric(),
1449 );
1450 }
1451 }
1452 }
1453 "length" | "char_length" | "character_length" => {
1454 if let Some(family) = arg_family(0) {
1455 check_function_argument(
1456 errors,
1457 strict,
1458 &name,
1459 0,
1460 family,
1461 "a string or binary argument",
1462 is_string_or_binary(family),
1463 );
1464 }
1465 }
1466 "upper" | "lower" | "trim" | "ltrim" | "rtrim" | "reverse" => {
1467 if let Some(family) = arg_family(0) {
1468 check_function_argument(
1469 errors,
1470 strict,
1471 &name,
1472 0,
1473 family,
1474 "a string argument",
1475 is_string_like(family),
1476 );
1477 }
1478 }
1479 "substring" | "substr" => {
1480 if let Some(family) = arg_family(0) {
1481 check_function_argument(
1482 errors,
1483 strict,
1484 &name,
1485 0,
1486 family,
1487 "a string argument",
1488 is_string_like(family),
1489 );
1490 }
1491 if let Some(family) = arg_family(1) {
1492 check_function_argument(
1493 errors,
1494 strict,
1495 &name,
1496 1,
1497 family,
1498 "a numeric argument",
1499 family.is_numeric(),
1500 );
1501 }
1502 if let Some(family) = arg_family(2) {
1503 check_function_argument(
1504 errors,
1505 strict,
1506 &name,
1507 2,
1508 family,
1509 "a numeric argument",
1510 family.is_numeric(),
1511 );
1512 }
1513 }
1514 "replace" => {
1515 for i in [0_usize, 1_usize, 2_usize] {
1516 if let Some(family) = arg_family(i) {
1517 check_function_argument(
1518 errors,
1519 strict,
1520 &name,
1521 i,
1522 family,
1523 "a string argument",
1524 is_string_like(family),
1525 );
1526 }
1527 }
1528 }
1529 "left" | "right" | "repeat" | "lpad" | "rpad" => {
1530 if let Some(family) = arg_family(0) {
1531 check_function_argument(
1532 errors,
1533 strict,
1534 &name,
1535 0,
1536 family,
1537 "a string argument",
1538 is_string_like(family),
1539 );
1540 }
1541 if let Some(family) = arg_family(1) {
1542 check_function_argument(
1543 errors,
1544 strict,
1545 &name,
1546 1,
1547 family,
1548 "a numeric argument",
1549 family.is_numeric(),
1550 );
1551 }
1552 if (name == "lpad" || name == "rpad") && function.args.len() > 2 {
1553 if let Some(family) = arg_family(2) {
1554 check_function_argument(
1555 errors,
1556 strict,
1557 &name,
1558 2,
1559 family,
1560 "a string argument",
1561 is_string_like(family),
1562 );
1563 }
1564 }
1565 }
1566 _ => {}
1567 }
1568}
1569
1570fn check_function_catalog(
1571 function: &Function,
1572 dialect: DialectType,
1573 function_catalog: Option<&dyn FunctionCatalog>,
1574 strict: bool,
1575 errors: &mut Vec<ValidationError>,
1576) {
1577 let Some(catalog) = function_catalog else {
1578 return;
1579 };
1580
1581 let raw_name = function_base_name(&function.name);
1582 let normalized_name = function_dispatch_name(&function.name);
1583 let arity = function.args.len();
1584 let Some(signatures) = catalog.lookup(dialect, raw_name, &normalized_name) else {
1585 errors.push(if strict {
1586 ValidationError::error(
1587 format!(
1588 "Unknown function '{}' for dialect {:?}",
1589 function.name, dialect
1590 ),
1591 validation_codes::E_UNKNOWN_FUNCTION,
1592 )
1593 } else {
1594 ValidationError::warning(
1595 format!(
1596 "Unknown function '{}' for dialect {:?}",
1597 function.name, dialect
1598 ),
1599 validation_codes::E_UNKNOWN_FUNCTION,
1600 )
1601 });
1602 return;
1603 };
1604
1605 if signatures.iter().any(|sig| sig.matches_arity(arity)) {
1606 return;
1607 }
1608
1609 let expected = signatures
1610 .iter()
1611 .map(|sig| sig.describe_arity())
1612 .collect::<Vec<_>>()
1613 .join(", ");
1614 errors.push(if strict {
1615 ValidationError::error(
1616 format!(
1617 "Invalid arity for function '{}': got {}, expected {}",
1618 function.name, arity, expected
1619 ),
1620 validation_codes::E_INVALID_FUNCTION_ARITY,
1621 )
1622 } else {
1623 ValidationError::warning(
1624 format!(
1625 "Invalid arity for function '{}': got {}, expected {}",
1626 function.name, arity, expected
1627 ),
1628 validation_codes::E_INVALID_FUNCTION_ARITY,
1629 )
1630 });
1631}
1632
1633#[derive(Debug, Clone, PartialEq, Eq, Hash)]
1634struct DeclaredRelationship {
1635 source_table: String,
1636 source_column: String,
1637 target_table: String,
1638 target_column: String,
1639}
1640
1641fn build_declared_relationships(
1642 schema: &ValidationSchema,
1643 schema_map: &HashMap<String, TableSchemaEntry>,
1644) -> Vec<DeclaredRelationship> {
1645 let mut relationships = HashSet::new();
1646
1647 for table in &schema.tables {
1648 let Some(source_key) =
1649 resolve_reference_table_key(&table.name, table.schema.as_deref(), None, schema_map)
1650 else {
1651 continue;
1652 };
1653
1654 for column in &table.columns {
1655 let Some(reference) = &column.references else {
1656 continue;
1657 };
1658 let Some(target_key) = resolve_reference_table_key(
1659 &reference.table,
1660 reference.schema.as_deref(),
1661 table.schema.as_deref(),
1662 schema_map,
1663 ) else {
1664 continue;
1665 };
1666
1667 relationships.insert(DeclaredRelationship {
1668 source_table: source_key.clone(),
1669 source_column: lower(&column.name),
1670 target_table: target_key,
1671 target_column: lower(&reference.column),
1672 });
1673 }
1674
1675 for foreign_key in &table.foreign_keys {
1676 if foreign_key.columns.len() != foreign_key.references.columns.len() {
1677 continue;
1678 }
1679 let Some(target_key) = resolve_reference_table_key(
1680 &foreign_key.references.table,
1681 foreign_key.references.schema.as_deref(),
1682 table.schema.as_deref(),
1683 schema_map,
1684 ) else {
1685 continue;
1686 };
1687
1688 for (source_col, target_col) in foreign_key
1689 .columns
1690 .iter()
1691 .zip(foreign_key.references.columns.iter())
1692 {
1693 relationships.insert(DeclaredRelationship {
1694 source_table: source_key.clone(),
1695 source_column: lower(source_col),
1696 target_table: target_key.clone(),
1697 target_column: lower(target_col),
1698 });
1699 }
1700 }
1701 }
1702
1703 relationships.into_iter().collect()
1704}
1705
1706fn resolve_column_binding(
1707 column: &Column,
1708 schema_map: &HashMap<String, TableSchemaEntry>,
1709 context: &TypeCheckContext,
1710 resolver: &mut Resolver<'_>,
1711) -> Option<(String, String)> {
1712 let column_name = lower(&column.name.name);
1713 if column_name.is_empty() {
1714 return None;
1715 }
1716
1717 if let Some(table) = &column.table {
1718 let mut table_key = lower(&table.name);
1719 if let Some(mapped) = context.table_aliases.get(&table_key) {
1720 table_key = mapped.clone();
1721 }
1722 if schema_map.contains_key(&table_key) {
1723 return Some((table_key, column_name));
1724 }
1725 return None;
1726 }
1727
1728 if let Some(resolved_source) = resolver.get_table(&column_name) {
1729 let mut table_key = lower(&resolved_source);
1730 if let Some(mapped) = context.table_aliases.get(&table_key) {
1731 table_key = mapped.clone();
1732 }
1733 if schema_map.contains_key(&table_key) {
1734 return Some((table_key, column_name));
1735 }
1736 }
1737
1738 let candidates: Vec<String> = context
1739 .referenced_tables
1740 .iter()
1741 .filter_map(|table_name| {
1742 schema_map
1743 .get(table_name)
1744 .filter(|entry| entry.columns.contains_key(&column_name))
1745 .map(|_| table_name.clone())
1746 })
1747 .collect();
1748 if candidates.len() == 1 {
1749 return Some((candidates[0].clone(), column_name));
1750 }
1751 None
1752}
1753
1754fn extract_join_equality_pairs(
1755 expr: &Expression,
1756 schema_map: &HashMap<String, TableSchemaEntry>,
1757 context: &TypeCheckContext,
1758 resolver: &mut Resolver<'_>,
1759 pairs: &mut Vec<((String, String), (String, String))>,
1760) {
1761 match expr {
1762 Expression::And(op) => {
1763 extract_join_equality_pairs(&op.left, schema_map, context, resolver, pairs);
1764 extract_join_equality_pairs(&op.right, schema_map, context, resolver, pairs);
1765 }
1766 Expression::Paren(paren) => {
1767 extract_join_equality_pairs(&paren.this, schema_map, context, resolver, pairs);
1768 }
1769 Expression::Eq(op) => {
1770 let (Expression::Column(left_col), Expression::Column(right_col)) =
1771 (&op.left, &op.right)
1772 else {
1773 return;
1774 };
1775 let Some(left) = resolve_column_binding(left_col, schema_map, context, resolver) else {
1776 return;
1777 };
1778 let Some(right) = resolve_column_binding(right_col, schema_map, context, resolver)
1779 else {
1780 return;
1781 };
1782 pairs.push((left, right));
1783 }
1784 _ => {}
1785 }
1786}
1787
1788fn relationship_matches_pair(
1789 relationship: &DeclaredRelationship,
1790 left_table: &str,
1791 left_column: &str,
1792 right_table: &str,
1793 right_column: &str,
1794) -> bool {
1795 (relationship.source_table == left_table
1796 && relationship.source_column == left_column
1797 && relationship.target_table == right_table
1798 && relationship.target_column == right_column)
1799 || (relationship.source_table == right_table
1800 && relationship.source_column == right_column
1801 && relationship.target_table == left_table
1802 && relationship.target_column == left_column)
1803}
1804
1805fn resolved_table_key_from_expr(
1806 expr: &Expression,
1807 schema_map: &HashMap<String, TableSchemaEntry>,
1808) -> Option<String> {
1809 match expr {
1810 Expression::Table(table) => resolve_table_schema_entry(table, schema_map).map(|(k, _)| k),
1811 Expression::Alias(alias) => resolved_table_key_from_expr(&alias.this, schema_map),
1812 _ => None,
1813 }
1814}
1815
1816fn select_from_table_keys(
1817 select: &crate::expressions::Select,
1818 schema_map: &HashMap<String, TableSchemaEntry>,
1819) -> HashSet<String> {
1820 let mut keys = HashSet::new();
1821 if let Some(from_clause) = &select.from {
1822 for expr in &from_clause.expressions {
1823 if let Some(key) = resolved_table_key_from_expr(expr, schema_map) {
1824 keys.insert(key);
1825 }
1826 }
1827 }
1828 keys
1829}
1830
1831fn is_natural_or_implied_join(kind: JoinKind) -> bool {
1832 matches!(
1833 kind,
1834 JoinKind::Natural
1835 | JoinKind::NaturalLeft
1836 | JoinKind::NaturalRight
1837 | JoinKind::NaturalFull
1838 | JoinKind::CrossApply
1839 | JoinKind::OuterApply
1840 | JoinKind::AsOf
1841 | JoinKind::AsOfLeft
1842 | JoinKind::AsOfRight
1843 | JoinKind::Lateral
1844 | JoinKind::LeftLateral
1845 )
1846}
1847
1848fn check_query_reference_quality(
1849 stmt: &Expression,
1850 schema_map: &HashMap<String, TableSchemaEntry>,
1851 resolver_schema: &MappingSchema,
1852 relationships: &[DeclaredRelationship],
1853) -> Vec<ValidationError> {
1854 let mut errors = Vec::new();
1855
1856 for node in stmt.dfs() {
1857 let Expression::Select(select) = node else {
1858 continue;
1859 };
1860
1861 let select_expr = Expression::Select(select.clone());
1862 let context = collect_type_check_context(&select_expr, schema_map);
1863 let scope = build_scope(&select_expr);
1864 let mut resolver = Resolver::new(&scope, resolver_schema, true);
1865
1866 let mut cumulative_left_tables = select_from_table_keys(select, schema_map);
1867
1868 for join in &select.joins {
1869 let right_table_key = resolved_table_key_from_expr(&join.this, schema_map);
1870 let has_explicit_condition = join.on.is_some() || !join.using.is_empty();
1871 let cartesian_like_kind = matches!(
1872 join.kind,
1873 JoinKind::Cross
1874 | JoinKind::Implicit
1875 | JoinKind::Array
1876 | JoinKind::LeftArray
1877 | JoinKind::Paste
1878 );
1879
1880 if right_table_key.is_some()
1881 && (cartesian_like_kind
1882 || (!has_explicit_condition && !is_natural_or_implied_join(join.kind)))
1883 {
1884 errors.push(ValidationError::warning(
1885 "Potential cartesian join: JOIN without ON/USING condition",
1886 validation_codes::W_CARTESIAN_JOIN,
1887 ));
1888 }
1889
1890 if let (Some(on_expr), Some(right_key)) = (&join.on, right_table_key.clone()) {
1891 if join.using.is_empty() {
1892 let mut eq_pairs = Vec::new();
1893 extract_join_equality_pairs(
1894 on_expr,
1895 schema_map,
1896 &context,
1897 &mut resolver,
1898 &mut eq_pairs,
1899 );
1900
1901 let relevant_relationships: Vec<&DeclaredRelationship> = relationships
1902 .iter()
1903 .filter(|rel| {
1904 cumulative_left_tables.contains(&rel.source_table)
1905 && rel.target_table == right_key
1906 || (cumulative_left_tables.contains(&rel.target_table)
1907 && rel.source_table == right_key)
1908 })
1909 .collect();
1910
1911 if !relevant_relationships.is_empty() {
1912 let uses_declared_fk = eq_pairs.iter().any(|((lt, lc), (rt, rc))| {
1913 relevant_relationships
1914 .iter()
1915 .any(|rel| relationship_matches_pair(rel, lt, lc, rt, rc))
1916 });
1917 if !uses_declared_fk {
1918 errors.push(ValidationError::warning(
1919 "JOIN predicate does not use declared foreign-key relationship columns",
1920 validation_codes::W_JOIN_NOT_USING_DECLARED_REFERENCE,
1921 ));
1922 }
1923 }
1924 }
1925 }
1926
1927 if let Some(right_key) = right_table_key {
1928 cumulative_left_tables.insert(right_key);
1929 }
1930 }
1931 }
1932
1933 errors
1934}
1935
1936fn are_setop_compatible(left: TypeFamily, right: TypeFamily) -> bool {
1937 if left == TypeFamily::Unknown || right == TypeFamily::Unknown {
1938 return true;
1939 }
1940 if left == right {
1941 return true;
1942 }
1943 if left.is_numeric() && right.is_numeric() {
1944 return true;
1945 }
1946 if left.is_temporal() && right.is_temporal() {
1947 return true;
1948 }
1949 false
1950}
1951
1952fn merged_setop_family(left: TypeFamily, right: TypeFamily) -> TypeFamily {
1953 if left == TypeFamily::Unknown {
1954 return right;
1955 }
1956 if right == TypeFamily::Unknown {
1957 return left;
1958 }
1959 if left == right {
1960 return left;
1961 }
1962 if left.is_numeric() && right.is_numeric() {
1963 if left == TypeFamily::Numeric || right == TypeFamily::Numeric {
1964 return TypeFamily::Numeric;
1965 }
1966 return TypeFamily::Integer;
1967 }
1968 if left.is_temporal() && right.is_temporal() {
1969 if left == TypeFamily::Timestamp || right == TypeFamily::Timestamp {
1970 return TypeFamily::Timestamp;
1971 }
1972 if left == TypeFamily::Date || right == TypeFamily::Date {
1973 return TypeFamily::Date;
1974 }
1975 return TypeFamily::Time;
1976 }
1977 TypeFamily::Unknown
1978}
1979
1980fn are_assignment_compatible(target: TypeFamily, source: TypeFamily) -> bool {
1981 if target == TypeFamily::Unknown || source == TypeFamily::Unknown {
1982 return true;
1983 }
1984 if target == source {
1985 return true;
1986 }
1987
1988 match target {
1989 TypeFamily::Boolean => source == TypeFamily::Boolean,
1990 TypeFamily::Integer | TypeFamily::Numeric => source.is_numeric(),
1991 TypeFamily::Date | TypeFamily::Time | TypeFamily::Timestamp | TypeFamily::Interval => {
1992 source.is_temporal()
1993 }
1994 TypeFamily::String => true,
1995 TypeFamily::Binary => matches!(source, TypeFamily::Binary | TypeFamily::String),
1996 TypeFamily::Json => matches!(source, TypeFamily::Json | TypeFamily::String),
1997 TypeFamily::Uuid => matches!(source, TypeFamily::Uuid | TypeFamily::String),
1998 TypeFamily::Array => source == TypeFamily::Array,
1999 TypeFamily::Map => source == TypeFamily::Map,
2000 TypeFamily::Struct => source == TypeFamily::Struct,
2001 TypeFamily::Unknown => true,
2002 }
2003}
2004
2005fn projection_families(
2006 query: &Expression,
2007 schema_map: &HashMap<String, TableSchemaEntry>,
2008 dialect: DialectType,
2009) -> Option<Vec<TypeFamily>> {
2010 let pair = match query {
2011 Expression::Union(query) => Some((&query.left, &query.right)),
2012 Expression::Intersect(query) => Some((&query.left, &query.right)),
2013 Expression::Except(query) => Some((&query.left, &query.right)),
2014 _ => None,
2015 };
2016 if let Some((left, right)) = pair {
2017 let left = projection_families(left, schema_map, dialect)?;
2018 let right = projection_families(right, schema_map, dialect)?;
2019 return match crate::set_operation::set_operation_layout(query, Some(dialect)).ok()? {
2020 Some(layout) => Some(
2021 layout
2022 .outputs
2023 .iter()
2024 .map(|output| {
2025 merged_setop_family(
2026 output
2027 .left_ordinal
2028 .and_then(|i| left.get(i).copied())
2029 .unwrap_or(TypeFamily::Unknown),
2030 output
2031 .right_ordinal
2032 .and_then(|i| right.get(i).copied())
2033 .unwrap_or(TypeFamily::Unknown),
2034 )
2035 })
2036 .collect(),
2037 ),
2038 None if left.len() == right.len() => Some(
2039 left.into_iter()
2040 .zip(right)
2041 .map(|(l, r)| merged_setop_family(l, r))
2042 .collect(),
2043 ),
2044 _ => None,
2045 };
2046 }
2047 match query {
2048 Expression::Select(select) => {
2049 if select
2050 .expressions
2051 .iter()
2052 .any(|e| matches!(e, Expression::Star(_) | Expression::BracedWildcard(_)))
2053 {
2054 return None;
2055 }
2056 let context = collect_type_check_context(query, schema_map);
2057 Some(
2058 select
2059 .expressions
2060 .iter()
2061 .map(|e| infer_expression_type_family(e, schema_map, &context))
2062 .collect(),
2063 )
2064 }
2065 Expression::Subquery(query) => projection_families(&query.this, schema_map, dialect),
2066 Expression::Paren(query) => projection_families(&query.this, schema_map, dialect),
2067 Expression::Values(values) => Some(
2068 values
2069 .expressions
2070 .first()?
2071 .expressions
2072 .iter()
2073 .map(|e| infer_expression_type_family(e, schema_map, &TypeCheckContext::default()))
2074 .collect(),
2075 ),
2076 _ => None,
2077 }
2078}
2079
2080fn check_set_operation_compatibility(
2081 query: &Expression,
2082 dialect: DialectType,
2083 op_name: &str,
2084 left_expr: &Expression,
2085 right_expr: &Expression,
2086 schema_map: &HashMap<String, TableSchemaEntry>,
2087 strict: bool,
2088 errors: &mut Vec<ValidationError>,
2089) {
2090 let Some(mut left_projection) = projection_families(left_expr, schema_map, dialect) else {
2091 return;
2092 };
2093 let Some(mut right_projection) = projection_families(right_expr, schema_map, dialect) else {
2094 return;
2095 };
2096
2097 match crate::set_operation::set_operation_layout(query, Some(dialect)) {
2098 Ok(Some(layout)) => {
2099 let left = left_projection;
2100 let right = right_projection;
2101 left_projection = layout
2102 .outputs
2103 .iter()
2104 .map(|output| {
2105 output
2106 .left_ordinal
2107 .and_then(|i| left.get(i).copied())
2108 .unwrap_or(TypeFamily::Unknown)
2109 })
2110 .collect();
2111 right_projection = layout
2112 .outputs
2113 .iter()
2114 .map(|output| {
2115 output
2116 .right_ordinal
2117 .and_then(|i| right.get(i).copied())
2118 .unwrap_or(TypeFamily::Unknown)
2119 })
2120 .collect();
2121 }
2122 Err(error) => {
2123 if !error.is_indeterminate() {
2124 errors.push(type_issue(
2125 strict,
2126 validation_codes::E_SETOP_ARITY_MISMATCH,
2127 validation_codes::W_SETOP_IMPLICIT_COERCION,
2128 error.to_string(),
2129 ));
2130 }
2131 return;
2132 }
2133 Ok(None) => {}
2134 }
2135 if left_projection.len() != right_projection.len() {
2136 errors.push(type_issue(
2137 strict,
2138 validation_codes::E_SETOP_ARITY_MISMATCH,
2139 validation_codes::W_SETOP_IMPLICIT_COERCION,
2140 format!(
2141 "{} operands return different column counts: left {}, right {}",
2142 op_name,
2143 left_projection.len(),
2144 right_projection.len()
2145 ),
2146 ));
2147 return;
2148 }
2149
2150 for (idx, (left, right)) in left_projection
2151 .into_iter()
2152 .zip(right_projection)
2153 .enumerate()
2154 {
2155 if !are_setop_compatible(left, right) {
2156 errors.push(type_issue(
2157 strict,
2158 validation_codes::E_SETOP_TYPE_MISMATCH,
2159 validation_codes::W_SETOP_IMPLICIT_COERCION,
2160 format!(
2161 "{} column {} has incompatible types: {} vs {}",
2162 op_name,
2163 idx + 1,
2164 type_family_name(left),
2165 type_family_name(right)
2166 ),
2167 ));
2168 }
2169 }
2170}
2171
2172fn check_insert_assignments(
2173 dialect: DialectType,
2174 stmt: &Expression,
2175 insert: &Insert,
2176 schema_map: &HashMap<String, TableSchemaEntry>,
2177 strict: bool,
2178 errors: &mut Vec<ValidationError>,
2179) {
2180 let Some((target_table_key, table_schema)) =
2181 resolve_table_schema_entry(&insert.table, schema_map)
2182 else {
2183 return;
2184 };
2185
2186 let mut target_columns = Vec::new();
2187 if insert.columns.is_empty() {
2188 target_columns.extend(table_schema.column_order.iter().cloned());
2189 } else {
2190 for column in &insert.columns {
2191 let col_name = lower(&column.name);
2192 if table_schema.columns.contains_key(&col_name) {
2193 target_columns.push(col_name);
2194 } else {
2195 errors.push(if strict {
2196 ValidationError::error(
2197 format!(
2198 "Unknown column '{}' in table '{}'",
2199 column.name, target_table_key
2200 ),
2201 validation_codes::E_UNKNOWN_COLUMN,
2202 )
2203 } else {
2204 ValidationError::warning(
2205 format!(
2206 "Unknown column '{}' in table '{}'",
2207 column.name, target_table_key
2208 ),
2209 validation_codes::E_UNKNOWN_COLUMN,
2210 )
2211 });
2212 }
2213 }
2214 }
2215
2216 if target_columns.is_empty() {
2217 return;
2218 }
2219
2220 let context = collect_type_check_context(stmt, schema_map);
2221
2222 if !insert.default_values {
2223 for (row_idx, row) in insert.values.iter().enumerate() {
2224 if row.len() != target_columns.len() {
2225 errors.push(type_issue(
2226 strict,
2227 validation_codes::E_INVALID_ASSIGNMENT_TYPE,
2228 validation_codes::W_IMPLICIT_CAST_ASSIGNMENT,
2229 format!(
2230 "INSERT row {} has {} values but target has {} columns",
2231 row_idx + 1,
2232 row.len(),
2233 target_columns.len()
2234 ),
2235 ));
2236 continue;
2237 }
2238
2239 for (value, target_column) in row.iter().zip(target_columns.iter()) {
2240 let Some(target_family) = table_schema.columns.get(target_column).copied() else {
2241 continue;
2242 };
2243 let source_family = infer_expression_type_family(value, schema_map, &context);
2244 if !are_assignment_compatible(target_family, source_family) {
2245 errors.push(type_issue(
2246 strict,
2247 validation_codes::E_INVALID_ASSIGNMENT_TYPE,
2248 validation_codes::W_IMPLICIT_CAST_ASSIGNMENT,
2249 format!(
2250 "INSERT assignment type mismatch for '{}.{}': expected {}, found {}",
2251 target_table_key,
2252 target_column,
2253 type_family_name(target_family),
2254 type_family_name(source_family)
2255 ),
2256 ));
2257 }
2258 }
2259 }
2260 }
2261
2262 if let Some(query) = &insert.query {
2263 if insert.by_name {
2265 return;
2266 }
2267
2268 let Some(source_projection) = projection_families(query, schema_map, dialect) else {
2269 return;
2270 };
2271
2272 if source_projection.len() != target_columns.len() {
2273 errors.push(type_issue(
2274 strict,
2275 validation_codes::E_INVALID_ASSIGNMENT_TYPE,
2276 validation_codes::W_IMPLICIT_CAST_ASSIGNMENT,
2277 format!(
2278 "INSERT source query has {} columns but target has {} columns",
2279 source_projection.len(),
2280 target_columns.len()
2281 ),
2282 ));
2283 return;
2284 }
2285
2286 for (source_family, target_column) in
2287 source_projection.into_iter().zip(target_columns.iter())
2288 {
2289 let Some(target_family) = table_schema.columns.get(target_column).copied() else {
2290 continue;
2291 };
2292 if !are_assignment_compatible(target_family, source_family) {
2293 errors.push(type_issue(
2294 strict,
2295 validation_codes::E_INVALID_ASSIGNMENT_TYPE,
2296 validation_codes::W_IMPLICIT_CAST_ASSIGNMENT,
2297 format!(
2298 "INSERT assignment type mismatch for '{}.{}': expected {}, found {}",
2299 target_table_key,
2300 target_column,
2301 type_family_name(target_family),
2302 type_family_name(source_family)
2303 ),
2304 ));
2305 }
2306 }
2307 }
2308}
2309
2310fn check_update_assignments(
2311 stmt: &Expression,
2312 update: &Update,
2313 schema_map: &HashMap<String, TableSchemaEntry>,
2314 strict: bool,
2315 errors: &mut Vec<ValidationError>,
2316) {
2317 let Some((target_table_key, table_schema)) =
2318 resolve_table_schema_entry(&update.table, schema_map)
2319 else {
2320 return;
2321 };
2322
2323 let context = collect_type_check_context(stmt, schema_map);
2324
2325 for (column, value) in &update.set {
2326 let col_name = lower(&column.name);
2327 let Some(target_family) = table_schema.columns.get(&col_name).copied() else {
2328 errors.push(if strict {
2329 ValidationError::error(
2330 format!(
2331 "Unknown column '{}' in table '{}'",
2332 column.name, target_table_key
2333 ),
2334 validation_codes::E_UNKNOWN_COLUMN,
2335 )
2336 } else {
2337 ValidationError::warning(
2338 format!(
2339 "Unknown column '{}' in table '{}'",
2340 column.name, target_table_key
2341 ),
2342 validation_codes::E_UNKNOWN_COLUMN,
2343 )
2344 });
2345 continue;
2346 };
2347
2348 let source_family = infer_expression_type_family(value, schema_map, &context);
2349 if !are_assignment_compatible(target_family, source_family) {
2350 errors.push(type_issue(
2351 strict,
2352 validation_codes::E_INVALID_ASSIGNMENT_TYPE,
2353 validation_codes::W_IMPLICIT_CAST_ASSIGNMENT,
2354 format!(
2355 "UPDATE assignment type mismatch for '{}.{}': expected {}, found {}",
2356 target_table_key,
2357 col_name,
2358 type_family_name(target_family),
2359 type_family_name(source_family)
2360 ),
2361 ));
2362 }
2363 }
2364}
2365
2366fn check_types(
2367 stmt: &Expression,
2368 dialect: DialectType,
2369 schema_map: &HashMap<String, TableSchemaEntry>,
2370 function_catalog: Option<&dyn FunctionCatalog>,
2371 strict: bool,
2372) -> Vec<ValidationError> {
2373 let mut errors = Vec::new();
2374 let context = collect_type_check_context(stmt, schema_map);
2375
2376 for node in stmt.dfs() {
2377 for (clause, predicate) in crate::binding::predicates(node) {
2378 let family = infer_expression_type_family(predicate, schema_map, &context);
2379 if !predicate_compatible(family, dialect) {
2380 errors.push(type_issue(
2381 strict,
2382 validation_codes::E_INVALID_PREDICATE_TYPE,
2383 validation_codes::W_PREDICATE_NULLABILITY,
2384 format!(
2385 "{clause} expects a boolean predicate, found {}",
2386 type_family_name(family)
2387 ),
2388 ));
2389 }
2390 }
2391 match node {
2392 Expression::Insert(insert) => {
2393 check_insert_assignments(dialect, stmt, insert, schema_map, strict, &mut errors);
2394 }
2395 Expression::Update(update) => {
2396 check_update_assignments(stmt, update, schema_map, strict, &mut errors);
2397 }
2398 Expression::Union(union) => {
2399 check_set_operation_compatibility(
2400 node,
2401 dialect,
2402 "UNION",
2403 &union.left,
2404 &union.right,
2405 schema_map,
2406 strict,
2407 &mut errors,
2408 );
2409 }
2410 Expression::Intersect(intersect) => {
2411 check_set_operation_compatibility(
2412 node,
2413 dialect,
2414 "INTERSECT",
2415 &intersect.left,
2416 &intersect.right,
2417 schema_map,
2418 strict,
2419 &mut errors,
2420 );
2421 }
2422 Expression::Except(except) => {
2423 check_set_operation_compatibility(
2424 node,
2425 dialect,
2426 "EXCEPT",
2427 &except.left,
2428 &except.right,
2429 schema_map,
2430 strict,
2431 &mut errors,
2432 );
2433 }
2434 Expression::And(op) | Expression::Or(op) => {
2435 for (side, expr) in [("left", &op.left), ("right", &op.right)] {
2436 let family = infer_expression_type_family(expr, schema_map, &context);
2437 if !predicate_compatible(family, dialect) {
2438 errors.push(type_issue(
2439 strict,
2440 validation_codes::E_INVALID_PREDICATE_TYPE,
2441 validation_codes::W_PREDICATE_NULLABILITY,
2442 format!(
2443 "Logical {} operand expects boolean, found {}",
2444 side,
2445 type_family_name(family)
2446 ),
2447 ));
2448 }
2449 }
2450 }
2451 Expression::Not(unary) => {
2452 let family = infer_expression_type_family(&unary.this, schema_map, &context);
2453 if !predicate_compatible(family, dialect) {
2454 errors.push(type_issue(
2455 strict,
2456 validation_codes::E_INVALID_PREDICATE_TYPE,
2457 validation_codes::W_PREDICATE_NULLABILITY,
2458 format!("NOT expects boolean, found {}", type_family_name(family)),
2459 ));
2460 }
2461 }
2462 Expression::Eq(op)
2463 | Expression::Neq(op)
2464 | Expression::Lt(op)
2465 | Expression::Lte(op)
2466 | Expression::Gt(op)
2467 | Expression::Gte(op) => {
2468 let left = infer_expression_type_family(&op.left, schema_map, &context);
2469 let right = infer_expression_type_family(&op.right, schema_map, &context);
2470 if !are_comparable(left, right) {
2471 errors.push(type_issue(
2472 strict,
2473 validation_codes::E_INCOMPATIBLE_COMPARISON_TYPES,
2474 validation_codes::W_IMPLICIT_CAST_COMPARISON,
2475 format!(
2476 "Incompatible comparison between {} and {}",
2477 type_family_name(left),
2478 type_family_name(right)
2479 ),
2480 ));
2481 }
2482 }
2483 Expression::Like(op) | Expression::ILike(op) => {
2484 let left = infer_expression_type_family(&op.left, schema_map, &context);
2485 let right = infer_expression_type_family(&op.right, schema_map, &context);
2486 if left != TypeFamily::Unknown
2487 && right != TypeFamily::Unknown
2488 && (!is_string_like(left) || !is_string_like(right))
2489 {
2490 errors.push(type_issue(
2491 strict,
2492 validation_codes::E_INCOMPATIBLE_COMPARISON_TYPES,
2493 validation_codes::W_IMPLICIT_CAST_COMPARISON,
2494 format!(
2495 "LIKE/ILIKE expects string operands, found {} and {}",
2496 type_family_name(left),
2497 type_family_name(right)
2498 ),
2499 ));
2500 }
2501 }
2502 Expression::Between(between) => {
2503 let this_family = infer_expression_type_family(&between.this, schema_map, &context);
2504 let low_family = infer_expression_type_family(&between.low, schema_map, &context);
2505 let high_family = infer_expression_type_family(&between.high, schema_map, &context);
2506
2507 if !are_comparable(this_family, low_family)
2508 || !are_comparable(this_family, high_family)
2509 {
2510 errors.push(type_issue(
2511 strict,
2512 validation_codes::E_INCOMPATIBLE_COMPARISON_TYPES,
2513 validation_codes::W_IMPLICIT_CAST_COMPARISON,
2514 format!(
2515 "BETWEEN bounds are incompatible with {} (found {} and {})",
2516 type_family_name(this_family),
2517 type_family_name(low_family),
2518 type_family_name(high_family)
2519 ),
2520 ));
2521 }
2522 }
2523 Expression::In(in_expr) => {
2524 let this_family = infer_expression_type_family(&in_expr.this, schema_map, &context);
2525 for value in &in_expr.expressions {
2526 let item_family = infer_expression_type_family(value, schema_map, &context);
2527 if !are_comparable(this_family, item_family) {
2528 errors.push(type_issue(
2529 strict,
2530 validation_codes::E_INCOMPATIBLE_COMPARISON_TYPES,
2531 validation_codes::W_IMPLICIT_CAST_COMPARISON,
2532 format!(
2533 "IN item type {} is incompatible with {}",
2534 type_family_name(item_family),
2535 type_family_name(this_family)
2536 ),
2537 ));
2538 break;
2539 }
2540 }
2541 }
2542 Expression::Add(op)
2543 | Expression::Sub(op)
2544 | Expression::Mul(op)
2545 | Expression::Div(op)
2546 | Expression::Mod(op) => {
2547 let left = infer_expression_type_family(&op.left, schema_map, &context);
2548 let right = infer_expression_type_family(&op.right, schema_map, &context);
2549
2550 if left == TypeFamily::Unknown || right == TypeFamily::Unknown {
2551 continue;
2552 }
2553
2554 let temporal_ok = matches!(node, Expression::Add(_) | Expression::Sub(_))
2555 && ((left.is_temporal() && right.is_numeric())
2556 || (right.is_temporal() && left.is_numeric())
2557 || (matches!(node, Expression::Sub(_))
2558 && left.is_temporal()
2559 && right.is_temporal()));
2560
2561 if !(left.is_numeric() && right.is_numeric()) && !temporal_ok {
2562 errors.push(type_issue(
2563 strict,
2564 validation_codes::E_INVALID_ARITHMETIC_TYPE,
2565 validation_codes::W_IMPLICIT_CAST_ARITHMETIC,
2566 format!(
2567 "Arithmetic operation expects numeric-compatible operands, found {} and {}",
2568 type_family_name(left),
2569 type_family_name(right)
2570 ),
2571 ));
2572 }
2573 }
2574 Expression::Function(function) => {
2575 check_function_catalog(function, dialect, function_catalog, strict, &mut errors);
2576 check_generic_function(function, schema_map, &context, strict, &mut errors);
2577 }
2578 Expression::Upper(func)
2579 | Expression::Lower(func)
2580 | Expression::LTrim(func)
2581 | Expression::RTrim(func)
2582 | Expression::Reverse(func) => {
2583 let family = infer_expression_type_family(&func.this, schema_map, &context);
2584 check_function_argument(
2585 &mut errors,
2586 strict,
2587 "string_function",
2588 0,
2589 family,
2590 "a string argument",
2591 is_string_like(family),
2592 );
2593 }
2594 Expression::Length(func) => {
2595 let family = infer_expression_type_family(&func.this, schema_map, &context);
2596 check_function_argument(
2597 &mut errors,
2598 strict,
2599 "length",
2600 0,
2601 family,
2602 "a string or binary argument",
2603 is_string_or_binary(family),
2604 );
2605 }
2606 Expression::Trim(func) => {
2607 let this_family = infer_expression_type_family(&func.this, schema_map, &context);
2608 check_function_argument(
2609 &mut errors,
2610 strict,
2611 "trim",
2612 0,
2613 this_family,
2614 "a string argument",
2615 is_string_like(this_family),
2616 );
2617 if let Some(chars) = &func.characters {
2618 let chars_family = infer_expression_type_family(chars, schema_map, &context);
2619 check_function_argument(
2620 &mut errors,
2621 strict,
2622 "trim",
2623 1,
2624 chars_family,
2625 "a string argument",
2626 is_string_like(chars_family),
2627 );
2628 }
2629 }
2630 Expression::Substring(func) => {
2631 let this_family = infer_expression_type_family(&func.this, schema_map, &context);
2632 check_function_argument(
2633 &mut errors,
2634 strict,
2635 "substring",
2636 0,
2637 this_family,
2638 "a string argument",
2639 is_string_like(this_family),
2640 );
2641
2642 let start_family = infer_expression_type_family(&func.start, schema_map, &context);
2643 check_function_argument(
2644 &mut errors,
2645 strict,
2646 "substring",
2647 1,
2648 start_family,
2649 "a numeric argument",
2650 start_family.is_numeric(),
2651 );
2652 if let Some(length) = &func.length {
2653 let length_family = infer_expression_type_family(length, schema_map, &context);
2654 check_function_argument(
2655 &mut errors,
2656 strict,
2657 "substring",
2658 2,
2659 length_family,
2660 "a numeric argument",
2661 length_family.is_numeric(),
2662 );
2663 }
2664 }
2665 Expression::Replace(func) => {
2666 for (arg, idx) in [
2667 (&func.this, 0_usize),
2668 (&func.old, 1_usize),
2669 (&func.new, 2_usize),
2670 ] {
2671 let family = infer_expression_type_family(arg, schema_map, &context);
2672 check_function_argument(
2673 &mut errors,
2674 strict,
2675 "replace",
2676 idx,
2677 family,
2678 "a string argument",
2679 is_string_like(family),
2680 );
2681 }
2682 }
2683 Expression::Left(func) | Expression::Right(func) => {
2684 let this_family = infer_expression_type_family(&func.this, schema_map, &context);
2685 check_function_argument(
2686 &mut errors,
2687 strict,
2688 "left_right",
2689 0,
2690 this_family,
2691 "a string argument",
2692 is_string_like(this_family),
2693 );
2694 let length_family =
2695 infer_expression_type_family(&func.length, schema_map, &context);
2696 check_function_argument(
2697 &mut errors,
2698 strict,
2699 "left_right",
2700 1,
2701 length_family,
2702 "a numeric argument",
2703 length_family.is_numeric(),
2704 );
2705 }
2706 Expression::Repeat(func) => {
2707 let this_family = infer_expression_type_family(&func.this, schema_map, &context);
2708 check_function_argument(
2709 &mut errors,
2710 strict,
2711 "repeat",
2712 0,
2713 this_family,
2714 "a string argument",
2715 is_string_like(this_family),
2716 );
2717 let times_family = infer_expression_type_family(&func.times, schema_map, &context);
2718 check_function_argument(
2719 &mut errors,
2720 strict,
2721 "repeat",
2722 1,
2723 times_family,
2724 "a numeric argument",
2725 times_family.is_numeric(),
2726 );
2727 }
2728 Expression::Lpad(func) | Expression::Rpad(func) => {
2729 let this_family = infer_expression_type_family(&func.this, schema_map, &context);
2730 check_function_argument(
2731 &mut errors,
2732 strict,
2733 "pad",
2734 0,
2735 this_family,
2736 "a string argument",
2737 is_string_like(this_family),
2738 );
2739 let length_family =
2740 infer_expression_type_family(&func.length, schema_map, &context);
2741 check_function_argument(
2742 &mut errors,
2743 strict,
2744 "pad",
2745 1,
2746 length_family,
2747 "a numeric argument",
2748 length_family.is_numeric(),
2749 );
2750 if let Some(fill) = &func.fill {
2751 let fill_family = infer_expression_type_family(fill, schema_map, &context);
2752 check_function_argument(
2753 &mut errors,
2754 strict,
2755 "pad",
2756 2,
2757 fill_family,
2758 "a string argument",
2759 is_string_like(fill_family),
2760 );
2761 }
2762 }
2763 Expression::Abs(func)
2764 | Expression::Sqrt(func)
2765 | Expression::Cbrt(func)
2766 | Expression::Ln(func)
2767 | Expression::Exp(func) => {
2768 let family = infer_expression_type_family(&func.this, schema_map, &context);
2769 check_function_argument(
2770 &mut errors,
2771 strict,
2772 "numeric_function",
2773 0,
2774 family,
2775 "a numeric argument",
2776 family.is_numeric(),
2777 );
2778 }
2779 Expression::Round(func) => {
2780 let this_family = infer_expression_type_family(&func.this, schema_map, &context);
2781 check_function_argument(
2782 &mut errors,
2783 strict,
2784 "round",
2785 0,
2786 this_family,
2787 "a numeric argument",
2788 this_family.is_numeric(),
2789 );
2790 if let Some(decimals) = &func.decimals {
2791 let decimals_family =
2792 infer_expression_type_family(decimals, schema_map, &context);
2793 check_function_argument(
2794 &mut errors,
2795 strict,
2796 "round",
2797 1,
2798 decimals_family,
2799 "a numeric argument",
2800 decimals_family.is_numeric(),
2801 );
2802 }
2803 }
2804 Expression::Floor(func) => {
2805 let this_family = infer_expression_type_family(&func.this, schema_map, &context);
2806 check_function_argument(
2807 &mut errors,
2808 strict,
2809 "floor",
2810 0,
2811 this_family,
2812 "a numeric argument",
2813 this_family.is_numeric(),
2814 );
2815 if let Some(scale) = &func.scale {
2816 let scale_family = infer_expression_type_family(scale, schema_map, &context);
2817 check_function_argument(
2818 &mut errors,
2819 strict,
2820 "floor",
2821 1,
2822 scale_family,
2823 "a numeric argument",
2824 scale_family.is_numeric(),
2825 );
2826 }
2827 }
2828 Expression::Ceil(func) => {
2829 let this_family = infer_expression_type_family(&func.this, schema_map, &context);
2830 check_function_argument(
2831 &mut errors,
2832 strict,
2833 "ceil",
2834 0,
2835 this_family,
2836 "a numeric argument",
2837 this_family.is_numeric(),
2838 );
2839 if let Some(decimals) = &func.decimals {
2840 let decimals_family =
2841 infer_expression_type_family(decimals, schema_map, &context);
2842 check_function_argument(
2843 &mut errors,
2844 strict,
2845 "ceil",
2846 1,
2847 decimals_family,
2848 "a numeric argument",
2849 decimals_family.is_numeric(),
2850 );
2851 }
2852 }
2853 Expression::Power(func) => {
2854 let left_family = infer_expression_type_family(&func.this, schema_map, &context);
2855 check_function_argument(
2856 &mut errors,
2857 strict,
2858 "power",
2859 0,
2860 left_family,
2861 "a numeric argument",
2862 left_family.is_numeric(),
2863 );
2864 let right_family =
2865 infer_expression_type_family(&func.expression, schema_map, &context);
2866 check_function_argument(
2867 &mut errors,
2868 strict,
2869 "power",
2870 1,
2871 right_family,
2872 "a numeric argument",
2873 right_family.is_numeric(),
2874 );
2875 }
2876 Expression::Log(func) => {
2877 let this_family = infer_expression_type_family(&func.this, schema_map, &context);
2878 check_function_argument(
2879 &mut errors,
2880 strict,
2881 "log",
2882 0,
2883 this_family,
2884 "a numeric argument",
2885 this_family.is_numeric(),
2886 );
2887 if let Some(base) = &func.base {
2888 let base_family = infer_expression_type_family(base, schema_map, &context);
2889 check_function_argument(
2890 &mut errors,
2891 strict,
2892 "log",
2893 1,
2894 base_family,
2895 "a numeric argument",
2896 base_family.is_numeric(),
2897 );
2898 }
2899 }
2900 _ => {}
2901 }
2902 }
2903
2904 errors
2905}
2906
2907fn predicate_compatible(family: TypeFamily, dialect: DialectType) -> bool {
2908 matches!(family, TypeFamily::Unknown | TypeFamily::Boolean)
2909 || (matches!(
2910 dialect,
2911 DialectType::MySQL | DialectType::SQLite | DialectType::DuckDB
2912 ) && family.is_numeric())
2913}
2914
2915mod semantics;
2916pub(crate) use semantics::check_semantics;
2917
2918fn resolve_scope_source_name(scope: &crate::scope::Scope, name: &str) -> Option<String> {
2919 scope
2920 .sources
2921 .get_key_value(name)
2922 .map(|(k, _)| k.clone())
2923 .or_else(|| {
2924 scope
2925 .sources
2926 .keys()
2927 .find(|source| source.eq_ignore_ascii_case(name))
2928 .cloned()
2929 })
2930}
2931
2932fn source_has_column(
2933 scope: &crate::scope::Scope,
2934 source: &str,
2935 columns: &[String],
2936 column: &crate::expressions::Identifier,
2937 dialect: DialectType,
2938) -> bool {
2939 let strategy = get_normalization_strategy(Some(dialect));
2940 let expected = normalize_identifier(column.clone(), strategy).name;
2941 columns.iter().any(|name| {
2942 if name == "*" {
2943 return true;
2944 }
2945 let identifier = scope
2946 .sources
2947 .get(source)
2948 .and_then(|source| source_output_identifier(&source.expression, name))
2949 .cloned()
2950 .unwrap_or_else(|| crate::binding::schema_identifier(name));
2951 normalize_identifier(identifier, strategy).name == expected
2952 })
2953}
2954
2955fn source_display_name(scope: &crate::scope::Scope, source_name: &str) -> String {
2956 scope
2957 .sources
2958 .get(source_name)
2959 .map(|source| match &source.expression {
2960 Expression::Table(table) => lower(&table_ref_display_name(table)),
2961 _ => lower(source_name),
2962 })
2963 .unwrap_or_else(|| lower(source_name))
2964}
2965
2966fn reference_diagnostic(
2968 message: String,
2969 code: &str,
2970 strict: bool,
2971 span: Option<crate::tokens::Span>,
2972) -> ValidationError {
2973 let mut error = if strict {
2974 ValidationError::error(message, code)
2975 } else {
2976 ValidationError::warning(message, code)
2977 };
2978 if let Some(span) = span {
2979 error = error
2980 .with_location(span.line, span.column)
2981 .with_span(Some(span.start), Some(span.end));
2982 }
2983 error
2984}
2985
2986use crate::scope::{scope_query, selected_reference_scope as selected_validation_scope};
2987
2988type ProjectionAliasBindings = HashMap<(usize, usize), DataType>;
2992
2993fn projection_reference_key(column: &Column) -> Option<(usize, usize)> {
2994 if column.table.is_some() {
2995 return None;
2996 }
2997 column
2998 .name
2999 .span
3000 .or(column.span)
3001 .map(|span| (span.start, span.end))
3002}
3003
3004use crate::binding::bound_identifier as validation_bound_identifier;
3005
3006fn apply_projection_alias_bindings(
3009 expression: Expression,
3010 bindings: &ProjectionAliasBindings,
3011) -> Expression {
3012 if bindings.is_empty() {
3013 return expression;
3014 }
3015 enum Task {
3016 Visit(Expression),
3017 Finish(Expression, usize),
3018 }
3019 let mut pending = vec![Task::Visit(expression)];
3020 let mut results = Vec::new();
3021 while let Some(task) = pending.pop() {
3022 match task {
3023 Task::Visit(mut expression) => {
3024 if let Expression::Column(column) = &expression {
3025 if let Some(data_type) =
3026 projection_reference_key(column).and_then(|key| bindings.get(&key))
3027 {
3028 results.push(validation_bound_identifier(column.name.clone(), data_type));
3029 continue;
3030 }
3031 }
3032 let mut children = Vec::new();
3033 crate::ast_children::for_each_child_mut(&mut expression, |child| {
3034 children.push(std::mem::replace(
3035 child,
3036 Expression::Null(crate::expressions::Null),
3037 ));
3038 });
3039 pending.push(Task::Finish(expression, children.len()));
3040 pending.extend(children.into_iter().rev().map(Task::Visit));
3041 }
3042 Task::Finish(mut expression, count) => {
3043 let mut children = results.split_off(results.len() - count).into_iter();
3044 crate::ast_children::for_each_child_mut(&mut expression, |child| {
3045 *child = children.next().expect("alias binding child");
3046 });
3047 results.push(expression);
3048 }
3049 }
3050 }
3051 results.pop().expect("alias binding result")
3052}
3053
3054fn bind_scope_projection_aliases(
3055 scope: &mut crate::scope::Scope,
3056 ancestors: &[&crate::scope::Scope],
3057 resolver_schema: &MappingSchema,
3058 dialect: DialectType,
3059 check_types: bool,
3060 bindings: &mut ProjectionAliasBindings,
3061) {
3062 let Expression::Select(select) = &scope.expression else {
3063 return;
3064 };
3065 if !select
3066 .expressions
3067 .iter()
3068 .any(|expression| matches!(expression, Expression::Alias(_)))
3069 {
3070 return;
3071 }
3072
3073 let strategy = get_normalization_strategy(Some(dialect));
3077 let mut inputs = HashSet::new();
3078 let mut open = false;
3079 for source_scope in std::iter::once(&*scope).chain(ancestors.iter().copied()) {
3080 let mut resolver = Resolver::new(source_scope, resolver_schema, true);
3081 for source in source_scope.sources.keys() {
3082 let columns = resolver.get_source_columns(source).unwrap_or_default();
3083 open |= columns.is_empty() || columns.iter().any(|name| name == "*");
3084 let expression = &source_scope.sources[source].expression;
3085 for name in columns {
3086 let identifier = source_output_identifier(expression, &name)
3087 .cloned()
3088 .unwrap_or_else(|| crate::expressions::Identifier::new(name));
3089 inputs.insert(normalize_identifier(identifier, strategy).name);
3090 }
3091 }
3092 }
3093 if open {
3094 return;
3095 }
3096
3097 let mut ctes: Vec<_> = if check_types {
3100 scope
3101 .cte_sources
3102 .values()
3103 .filter_map(|source| match &source.expression {
3104 Expression::Cte(cte) => Some(cte.as_ref().clone()),
3105 _ => None,
3106 })
3107 .collect()
3108 } else {
3109 Vec::new()
3110 };
3111 ctes.sort_by_key(|cte| cte.alias.span.map(|span| span.start));
3112 let mut aliases = HashMap::<String, Option<DataType>>::new();
3113 let Expression::Select(select) = &mut scope.expression else {
3114 unreachable!()
3115 };
3116 let mut projections = std::mem::take(&mut select.expressions);
3119 let mut type_select = select.clone();
3120 if !ctes.is_empty() {
3121 type_select.with = Some(crate::expressions::With {
3122 ctes,
3123 recursive: false,
3124 leading_comments: Vec::new(),
3125 search: None,
3126 });
3127 }
3128 for projection in projections.iter_mut() {
3129 let mut references = ProjectionAliasBindings::new();
3130 for node in walk_in_scope(projection, false) {
3131 if !crate::binding::lateral_aliases(dialect) {
3132 break;
3133 }
3134 let Expression::Column(column) = node else {
3135 continue;
3136 };
3137 let Some(key) = projection_reference_key(column) else {
3138 continue;
3139 };
3140 let name = normalize_identifier(column.name.clone(), strategy).name;
3141 if inputs.contains(&name) {
3142 continue;
3143 }
3144 if let Some(Some(data_type)) = aliases.get(&name) {
3145 references.insert(key, data_type.clone());
3146 }
3147 }
3148 let original = std::mem::replace(projection, Expression::Null(crate::expressions::Null));
3149 *projection = apply_projection_alias_bindings(original, &references);
3150 bindings.extend(references);
3151 if let Expression::Alias(alias) = projection {
3152 let name = normalize_identifier(alias.alias.clone(), strategy).name;
3153 if aliases.contains_key(&name) {
3155 aliases.insert(name, None);
3156 continue;
3157 }
3158 if !check_types {
3159 aliases.insert(name, Some(DataType::Unknown));
3160 continue;
3161 }
3162 let mut typed = Expression::Select(type_select.clone());
3163 if let Expression::Select(query) = &mut typed {
3164 query.expressions.push(alias.this.clone());
3165 }
3166 typed = apply_projection_alias_bindings(typed, bindings);
3169 annotate_types(&mut typed, Some(resolver_schema), Some(dialect));
3170 let data_type = match typed {
3171 Expression::Select(query) => query.expressions[0]
3172 .inferred_type()
3173 .cloned()
3174 .unwrap_or(DataType::Unknown),
3175 _ => unreachable!(),
3176 };
3177 aliases.insert(name, Some(data_type));
3178 }
3179 }
3180 select.expressions = projections;
3181 use crate::binding::{clause_aliases, AliasClause};
3184 let mut clauses = Vec::new();
3185 if clause_aliases(dialect, AliasClause::Where) {
3186 if let Some(clause) = &mut select.where_clause {
3187 clauses.push(&mut clause.this);
3188 }
3189 }
3190 if clause_aliases(dialect, AliasClause::Group) {
3191 if let Some(clause) = &mut select.group_by {
3192 clauses.extend(clause.expressions.iter_mut());
3193 }
3194 }
3195 if clause_aliases(dialect, AliasClause::Having) {
3196 if let Some(clause) = &mut select.having {
3197 clauses.push(&mut clause.this);
3198 }
3199 }
3200 if clause_aliases(dialect, AliasClause::Qualify) {
3201 if let Some(clause) = &mut select.qualify {
3202 clauses.push(&mut clause.this);
3203 }
3204 }
3205 for clause in clauses {
3206 let mut references = ProjectionAliasBindings::new();
3207 for node in walk_in_scope(clause, false) {
3208 let Expression::Column(column) = node else {
3209 continue;
3210 };
3211 let Some(key) = projection_reference_key(column) else {
3212 continue;
3213 };
3214 let name = normalize_identifier(column.name.clone(), strategy).name;
3215 if !inputs.contains(&name) {
3216 if let Some(Some(data_type)) = aliases.get(&name) {
3217 references.insert(key, data_type.clone());
3218 }
3219 }
3220 }
3221 let original = std::mem::replace(clause, Expression::Null(crate::expressions::Null));
3222 *clause = apply_projection_alias_bindings(original, &references);
3223 bindings.extend(references);
3224 }
3225}
3226
3227fn source_output_identifier<'a>(
3230 expression: &'a Expression,
3231 name: &str,
3232) -> Option<&'a crate::expressions::Identifier> {
3233 let columns: &[crate::expressions::Identifier] = match expression {
3234 Expression::Cte(cte) => &cte.columns,
3235 Expression::Subquery(query) => &query.column_aliases,
3236 Expression::Alias(alias) => &alias.column_aliases,
3237 Expression::Table(table) => &table.column_aliases,
3238 Expression::Paren(paren) => return source_output_identifier(&paren.this, name),
3239 _ => &[],
3240 };
3241 if !columns.is_empty() {
3242 return columns.iter().find(|column| column.name == name);
3243 }
3244 match scope_query(expression) {
3245 Expression::Select(select) => select.expressions.iter().find_map(|projection| {
3246 let identifier = match projection {
3247 Expression::Alias(alias) => &alias.alias,
3248 Expression::Column(column) => &column.name,
3249 Expression::Identifier(identifier) => identifier,
3250 _ => return None,
3251 };
3252 (identifier.name == name).then_some(identifier)
3253 }),
3254 Expression::Union(query) => source_output_identifier(&query.left, name),
3255 Expression::Intersect(query) => source_output_identifier(&query.left, name),
3256 Expression::Except(query) => source_output_identifier(&query.left, name),
3257 _ => None,
3258 }
3259}
3260
3261use crate::binding::bind_lambdas as bind_validation_lambdas;
3267
3268#[derive(Clone, Copy)]
3269struct ReferenceValidationOptions {
3270 dialect: DialectType,
3271 strict: bool,
3272 check_references: bool,
3273 check_types: bool,
3274}
3275
3276#[derive(Clone, Copy, PartialEq, Eq)]
3277enum OutputNameResolution {
3278 InputOnly,
3279 OutputFirst,
3280 InputFirst,
3281}
3282
3283fn order_by_validation_columns(
3286 order_by: &crate::expressions::OrderBy,
3287 dialect: DialectType,
3288) -> Vec<(&Column, OutputNameResolution)> {
3289 let scalar_resolution = match dialect {
3293 DialectType::DuckDB => OutputNameResolution::InputFirst,
3296 DialectType::Snowflake | DialectType::BigQuery => OutputNameResolution::OutputFirst,
3297 _ => OutputNameResolution::InputOnly,
3298 };
3299 let mut columns = Vec::new();
3300 let mut pending: Vec<_> = order_by
3301 .expressions
3302 .iter()
3303 .rev()
3304 .map(|ordered| (&ordered.this, OutputNameResolution::OutputFirst))
3305 .collect();
3306 while let Some((node, resolution)) = pending.pop() {
3307 match node {
3308 Expression::Select(_)
3311 | Expression::Union(_)
3312 | Expression::Intersect(_)
3313 | Expression::Except(_)
3314 | Expression::Subquery(_)
3315 | Expression::Exists(_)
3316 | Expression::Cte(_) => continue,
3317 Expression::Column(column) => columns.push((column.as_ref(), resolution)),
3318 Expression::Paren(paren) => pending.push((&paren.this, resolution)),
3319 _ => {
3320 let input_only = resolution == OutputNameResolution::InputOnly
3321 || matches!(
3322 node,
3323 Expression::WindowFunction(_)
3324 | Expression::WithinGroup(_)
3325 | Expression::Filter(_)
3326 )
3327 || crate::traversal::is_aggregate(node);
3328 if input_only {
3329 columns.extend(walk_in_scope(node, false).filter_map(|node| match node {
3330 Expression::Column(column) => {
3331 Some((column.as_ref(), OutputNameResolution::InputOnly))
3332 }
3333 _ => None,
3334 }));
3335 } else {
3336 pending.extend(
3337 node.children()
3338 .into_iter()
3339 .rev()
3340 .map(|child| (child, scalar_resolution)),
3341 );
3342 }
3343 }
3344 }
3345 }
3346 columns
3347}
3348
3349fn validate_scope_columns(
3350 scope: &crate::scope::Scope,
3351 ancestors: &[&crate::scope::Scope],
3352 schema_map: &HashMap<String, TableSchemaEntry>,
3353 resolver_schema: &MappingSchema,
3354 options: ReferenceValidationOptions,
3355) -> Vec<ValidationError> {
3356 let ReferenceValidationOptions {
3357 dialect,
3358 strict,
3359 check_references,
3360 ..
3361 } = options;
3362 let mut errors = Vec::new();
3363 let Expression::Select(select) = &scope.expression else {
3364 return errors;
3365 };
3366
3367 let mut visible = crate::scope::Scope::new(scope.expression.clone());
3370 for source_scope in std::iter::once(scope).chain(ancestors.iter().copied()) {
3371 for (name, source) in &source_scope.sources {
3372 if resolve_scope_source_name(&visible, name).is_none() {
3373 visible.sources.insert(name.clone(), source.clone());
3374 }
3375 }
3376 }
3377 let mut normalized = select.clone();
3378 let mut normalizer = Resolver::new(&visible, resolver_schema, true);
3379 let _ = normalize_dotted_columns_in_scope(&mut normalized, &visible, &mut normalizer);
3380 let strategy = get_normalization_strategy(Some(dialect));
3381 let mut output_names = HashMap::<String, usize>::new();
3382 for projection in &normalized.expressions {
3383 let identifier = match projection {
3384 Expression::Star(_) => {
3388 output_names.clear();
3389 break;
3390 }
3391 Expression::Alias(alias) => &alias.alias,
3392 Expression::Column(column) if column.name.name != "*" => &column.name,
3393 Expression::Column(_) => {
3394 output_names.clear();
3395 break;
3396 }
3397 _ => continue,
3398 };
3399 let name = normalize_identifier(identifier.clone(), strategy).name;
3400 *output_names.entry(name).or_default() += 1;
3401 }
3402 let order_by = normalized.order_by.take();
3406 let expression = Expression::Select(normalized);
3407 let mut resolvers: Vec<_> = std::iter::once(scope)
3408 .chain(ancestors.iter().copied())
3409 .map(|scope| (scope, Resolver::new(scope, resolver_schema, true)))
3410 .collect();
3411 let using_columns: HashSet<String> = select
3412 .joins
3413 .iter()
3414 .flat_map(|join| join.using.iter().map(|id| lower(&id.name)))
3415 .collect();
3416
3417 let input_columns = walk_in_scope(&expression, false).filter_map(|node| match node {
3418 Expression::Column(column) => Some((column.as_ref(), OutputNameResolution::InputOnly)),
3419 _ => None,
3420 });
3421 let order_columns = order_by
3422 .as_ref()
3423 .into_iter()
3424 .flat_map(|order| order_by_validation_columns(order, dialect));
3425 for (column, resolution) in input_columns.chain(order_columns) {
3426 let output_matches =
3427 if resolution != OutputNameResolution::InputOnly && column.table.is_none() {
3428 let name = normalize_identifier(column.name.clone(), strategy).name;
3429 output_names.get(&name) == Some(&1)
3432 } else {
3433 false
3434 };
3435 if output_matches && resolution == OutputNameResolution::OutputFirst {
3436 continue;
3437 }
3438 let name = lower(&column.name.name);
3439 if name.is_empty() || name == "*" {
3440 continue;
3441 }
3442 let span = column.name.span.or(column.span);
3443 if let Some(qualifier) = &column.table {
3444 let resolved = resolvers.iter_mut().find_map(|(scope, resolver)| {
3445 resolve_scope_source_name(scope, &qualifier.name).map(|source| {
3446 (
3447 *scope,
3448 resolver.get_source_columns(&source).unwrap_or_default(),
3449 source,
3450 )
3451 })
3452 });
3453 if let Some((source_scope, columns, source)) = resolved {
3454 if !columns.is_empty()
3455 && !source_has_column(source_scope, &source, &columns, &column.name, dialect)
3456 {
3457 errors.push(reference_diagnostic(
3458 format!(
3459 "Unknown column '{}' in table '{}'",
3460 name,
3461 source_display_name(source_scope, &source)
3462 ),
3463 validation_codes::E_UNKNOWN_COLUMN,
3464 strict,
3465 span,
3466 ));
3467 }
3468 } else {
3469 errors.push(reference_diagnostic(
3470 format!(
3471 "Unknown table or alias '{}' referenced by column '{}'",
3472 qualifier.name, name
3473 ),
3474 validation_codes::E_UNRESOLVED_REFERENCE,
3475 strict,
3476 qualifier.span.or(column.span),
3477 ));
3478 }
3479 continue;
3480 }
3481
3482 let mut found = false;
3483 for (source_scope, resolver) in &mut resolvers {
3484 let mut matches = 0;
3485 let mut open = false;
3486 for source in source_scope.sources.keys() {
3487 let columns = resolver.get_source_columns(source).unwrap_or_default();
3488 open |= columns.is_empty() || columns.iter().any(|name| name == "*");
3489 matches += usize::from(
3491 !columns.iter().any(|name| name == "*")
3492 && source_has_column(source_scope, source, &columns, &column.name, dialect),
3493 );
3494 }
3495 if matches > 0 || open {
3496 if matches > 1 && check_references && !using_columns.contains(&name) {
3497 errors.push(reference_diagnostic(
3498 format!(
3499 "Ambiguous unqualified column '{}' found in {} referenced tables",
3500 name, matches
3501 ),
3502 if strict {
3503 validation_codes::E_AMBIGUOUS_COLUMN_REFERENCE
3504 } else {
3505 validation_codes::W_WEAK_REFERENCE_INTEGRITY
3506 },
3507 strict,
3508 span,
3509 ));
3510 }
3511 found = true;
3512 break;
3513 }
3514 }
3515 if !found {
3516 if output_matches && resolution == OutputNameResolution::InputFirst {
3517 continue;
3518 }
3519 if scope.sources.is_empty()
3523 && ancestors.is_empty()
3524 && (schema_map.is_empty()
3525 || schema_map
3526 .values()
3527 .any(|table| table.columns.contains_key(&name)))
3528 {
3529 continue;
3530 }
3531 let message = if scope.sources.len() == 1 {
3532 let source = scope.sources.keys().next().unwrap();
3533 format!(
3534 "Unknown column '{}' in table '{}'",
3535 name,
3536 source_display_name(scope, source)
3537 )
3538 } else {
3539 format!(
3540 "Unknown column '{}' (not found in any referenced table)",
3541 name
3542 )
3543 };
3544 errors.push(reference_diagnostic(
3545 message,
3546 validation_codes::E_UNKNOWN_COLUMN,
3547 strict,
3548 span,
3549 ));
3550 }
3551 }
3552 errors
3553}
3554
3555fn validate_scope_tree(
3556 scope: &crate::scope::Scope,
3557 ancestors: &[&crate::scope::Scope],
3558 schema_map: &HashMap<String, TableSchemaEntry>,
3559 resolver_schema: &MappingSchema,
3560 options: ReferenceValidationOptions,
3561 errors: &mut Vec<ValidationError>,
3562 bindings: &mut ProjectionAliasBindings,
3563) {
3564 let mut selected = selected_validation_scope(scope);
3565 let mut child_errors = Vec::new();
3568 for child in scope
3571 .cte_scopes
3572 .iter()
3573 .chain(&scope.derived_table_scopes)
3574 .chain(&scope.union_scopes)
3575 {
3576 validate_scope_tree(
3577 child,
3578 ancestors,
3579 schema_map,
3580 resolver_schema,
3581 options,
3582 &mut child_errors,
3583 bindings,
3584 );
3585 }
3586 let outer: Vec<_> = std::iter::once(&selected)
3587 .chain(ancestors.iter().copied())
3588 .collect();
3589 for child in scope.subquery_scopes.iter().chain(&scope.udtf_scopes) {
3590 validate_scope_tree(
3591 child,
3592 &outer,
3593 schema_map,
3594 resolver_schema,
3595 options,
3596 &mut child_errors,
3597 bindings,
3598 );
3599 }
3600 bind_scope_projection_aliases(
3601 &mut selected,
3602 ancestors,
3603 resolver_schema,
3604 options.dialect,
3605 options.check_types,
3606 bindings,
3607 );
3608 for node in walk_in_scope(&selected.expression, false) {
3609 let Expression::Table(table) = node else {
3610 continue;
3611 };
3612 let is_cte = table.schema.is_none()
3613 && table.catalog.is_none()
3614 && selected
3615 .cte_sources
3616 .keys()
3617 .any(|name| name.eq_ignore_ascii_case(&table.name.name));
3618 if !is_cte {
3619 validate_schema_table(table, schema_map, options.strict, errors);
3620 }
3621 }
3622 errors.extend(validate_scope_columns(
3623 &selected,
3624 ancestors,
3625 schema_map,
3626 resolver_schema,
3627 options,
3628 ));
3629 errors.extend(child_errors);
3630}
3631
3632fn validate_schema_table(
3633 table: &TableRef,
3634 schema_map: &HashMap<String, TableSchemaEntry>,
3635 strict: bool,
3636 errors: &mut Vec<ValidationError>,
3637) {
3638 if !table_ref_candidates(table)
3639 .iter()
3640 .any(|key| schema_map.contains_key(key))
3641 {
3642 errors.push(reference_diagnostic(
3643 format!("Unknown table '{}'", table_ref_display_name(table)),
3644 validation_codes::E_UNKNOWN_TABLE,
3645 strict,
3646 table.name.span,
3647 ));
3648 }
3649}
3650
3651fn validate_statement_with_schema(
3652 stmt: &Expression,
3653 schema_map: &HashMap<String, TableSchemaEntry>,
3654 resolver_schema: &MappingSchema,
3655 options: ReferenceValidationOptions,
3656 bindings: &mut ProjectionAliasBindings,
3657) -> Vec<ValidationError> {
3658 let mut errors = Vec::new();
3659 let mut pending = vec![stmt];
3661 while let Some(expression) = pending.pop() {
3662 if let Some(select) = crate::binding::dml_scope(expression) {
3663 let query = Expression::Select(Box::new(select));
3664 let scope = build_scope(&query);
3665 validate_scope_tree(
3666 &scope,
3667 &[],
3668 schema_map,
3669 resolver_schema,
3670 options,
3671 &mut errors,
3672 bindings,
3673 );
3674 let target_columns = match expression {
3675 Expression::Insert(insert) => {
3676 Some((&insert.table, insert.columns.iter().collect::<Vec<_>>()))
3677 }
3678 Expression::Update(update) => Some((
3679 &update.table,
3680 update.set.iter().map(|(column, _)| column).collect(),
3681 )),
3682 _ => None,
3683 };
3684 if let Some((table, columns)) = target_columns {
3685 if let Some((_, entry)) = resolve_table_schema_entry(table, schema_map) {
3686 for column in columns {
3687 if !entry.columns.is_empty()
3688 && !entry.columns.contains_key("*")
3689 && !entry.columns.contains_key(&lower(&column.name))
3690 {
3691 errors.push(reference_diagnostic(
3692 format!(
3693 "Unknown column '{}' in table '{}'",
3694 column.name, table.name.name
3695 ),
3696 validation_codes::E_UNKNOWN_COLUMN,
3697 options.strict,
3698 column.span,
3699 ));
3700 }
3701 }
3702 }
3703 }
3704 if let Expression::Insert(insert) = expression {
3705 for value in insert.values.iter().flatten() {
3707 for node in walk_in_scope(value, false) {
3708 if let Expression::Column(column) = node {
3709 errors.push(reference_diagnostic(
3710 format!("Unknown column '{}' in INSERT VALUES", column.name.name),
3711 validation_codes::E_UNKNOWN_COLUMN,
3712 options.strict,
3713 column.name.span.or(column.span),
3714 ));
3715 }
3716 }
3717 pending.extend(value.children().into_iter().filter(|child| {
3718 matches!(child, Expression::Subquery(_) | Expression::Select(_))
3719 }));
3720 }
3721 if let Some(query) = &insert.query {
3722 let mut source = crate::expressions::Select::new();
3724 source.with = insert.with.clone();
3725 source.expressions.push(query.clone());
3726 let scope = build_scope(&Expression::Select(Box::new(source)));
3727 validate_scope_tree(
3728 &scope,
3729 &[],
3730 schema_map,
3731 resolver_schema,
3732 options,
3733 &mut errors,
3734 bindings,
3735 );
3736 }
3737 }
3738 if let Expression::Merge(merge) = expression {
3739 let target = match merge.this.as_ref() {
3740 Expression::Table(table) => Some(table.as_ref()),
3741 Expression::Alias(alias) => match &alias.this {
3742 Expression::Table(table) => Some(table.as_ref()),
3743 _ => None,
3744 },
3745 _ => None,
3746 };
3747 if let Some((table, (_, entry))) = target.and_then(|table| {
3748 resolve_table_schema_entry(table, schema_map).map(|entry| (table, entry))
3749 }) {
3750 if let Some(whens) = &merge.whens {
3751 for node in whens.dfs() {
3752 let Expression::When(when) = node else {
3753 continue;
3754 };
3755 let Expression::Tuple(action) = when.then.as_ref() else {
3756 continue;
3757 };
3758 let Some(Expression::Var(kind)) = action.expressions.first() else {
3759 continue;
3760 };
3761 let Some(Expression::Tuple(assignments)) = action.expressions.get(1)
3762 else {
3763 continue;
3764 };
3765 for assignment in &assignments.expressions {
3766 let target = if kind.this.eq_ignore_ascii_case("UPDATE") {
3767 if let Expression::Eq(eq) = assignment {
3768 &eq.left
3769 } else {
3770 continue;
3771 }
3772 } else if kind.this.eq_ignore_ascii_case("INSERT") {
3773 assignment
3774 } else {
3775 continue;
3776 };
3777 let identifier = match target {
3778 Expression::Identifier(id) => id,
3779 Expression::Column(column) => &column.name,
3780 _ => continue,
3781 };
3782 if !entry.columns.is_empty()
3783 && !entry.columns.contains_key("*")
3784 && !entry.columns.contains_key(&lower(&identifier.name))
3785 {
3786 errors.push(reference_diagnostic(
3787 format!(
3788 "Unknown column '{}' in table '{}'",
3789 identifier.name, table.name.name
3790 ),
3791 validation_codes::E_UNKNOWN_COLUMN,
3792 options.strict,
3793 identifier.span,
3794 ));
3795 }
3796 }
3797 }
3798 }
3799 }
3800 }
3801 continue;
3802 }
3803 if matches!(
3804 scope_query(expression),
3805 Expression::Select(_)
3806 | Expression::Union(_)
3807 | Expression::Intersect(_)
3808 | Expression::Except(_)
3809 ) {
3810 let scope = build_scope(expression);
3811 validate_scope_tree(
3812 &scope,
3813 &[],
3814 schema_map,
3815 resolver_schema,
3816 options,
3817 &mut errors,
3818 bindings,
3819 );
3820 } else {
3821 if let Expression::CreateTable(create) = expression {
3823 if let Some(query) = &create.as_select {
3824 pending.push(query);
3825 }
3826 if let Some(source) = &create.clone_source {
3827 validate_schema_table(source, schema_map, options.strict, &mut errors);
3828 }
3829 continue;
3830 }
3831 if let Expression::Table(table) = expression {
3832 validate_schema_table(table, schema_map, options.strict, &mut errors);
3833 }
3834 pending.extend(expression.children().into_iter().rev());
3835 }
3836 }
3837 errors
3838}
3839
3840pub fn validate_with_schema(
3842 sql: &str,
3843 dialect: DialectType,
3844 schema: &ValidationSchema,
3845 options: &SchemaValidationOptions,
3846) -> ValidationResult {
3847 let strict = options.strict.unwrap_or(schema.strict.unwrap_or(true));
3848
3849 let d = Dialect::get(dialect);
3851 let statements = match crate::parse_for_validation(
3852 sql,
3853 &d,
3854 &crate::ValidationOptions {
3855 complexity_guard: options.complexity_guard,
3856 strict_syntax: options.strict_syntax,
3857 semantic: options.semantic,
3858 },
3859 ) {
3860 Ok(exprs) => exprs,
3861 Err(result) => return result,
3862 };
3863
3864 let schema_map = build_schema_map(schema);
3865 let resolver_schema = mapping_schema_from_validation_schema_with_dialect(schema, dialect);
3866 let mut all_errors = Vec::new();
3867 let embedded_function_catalog = if options.check_types && options.function_catalog.is_none() {
3868 default_embedded_function_catalog()
3869 } else {
3870 None
3871 };
3872 let effective_function_catalog = options
3873 .function_catalog
3874 .as_deref()
3875 .or_else(|| embedded_function_catalog.as_deref());
3876 let declared_relationships = if options.check_references {
3877 build_declared_relationships(schema, &schema_map)
3878 } else {
3879 Vec::new()
3880 };
3881
3882 if options.check_references {
3883 all_errors.extend(check_reference_integrity(schema, &schema_map, strict));
3884 }
3885
3886 for mut statement in statements {
3887 if options.semantic {
3888 all_errors.extend(check_semantics(&statement, dialect, Some(schema)));
3889 }
3890 crate::binding::bind_dml_pseudoreferences(&mut statement);
3891 let statement = bind_validation_lambdas(statement, dialect);
3892 let mut bindings = ProjectionAliasBindings::new();
3893 all_errors.extend(validate_statement_with_schema(
3894 &statement,
3895 &schema_map,
3896 &resolver_schema,
3897 ReferenceValidationOptions {
3898 dialect,
3899 strict,
3900 check_references: options.check_references,
3901 check_types: options.check_types,
3902 },
3903 &mut bindings,
3904 ));
3905 let mut statement = apply_projection_alias_bindings(statement, &bindings);
3906 if options.check_types {
3907 if statement.dfs().any(|node| {
3910 matches!(
3911 node,
3912 Expression::Union(_) | Expression::Intersect(_) | Expression::Except(_)
3913 )
3914 }) && statement
3915 .dfs()
3916 .any(|node| matches!(node, Expression::Star(_)))
3917 {
3918 if let Ok(qualified) = crate::optimizer::qualify_schema_aware_expression(
3919 statement.clone(),
3920 &resolver_schema,
3921 Some(dialect),
3922 ) {
3923 statement = qualified;
3924 }
3925 }
3926 annotate_types(&mut statement, Some(&resolver_schema), Some(dialect));
3927 }
3928 let stmt = &statement;
3929 if options.check_types {
3930 all_errors.extend(check_types(
3931 stmt,
3932 dialect,
3933 &schema_map,
3934 effective_function_catalog,
3935 strict,
3936 ));
3937 }
3938 if options.check_references {
3939 all_errors.extend(check_query_reference_quality(
3940 stmt,
3941 &schema_map,
3942 &resolver_schema,
3943 &declared_relationships,
3944 ));
3945 }
3946 }
3947
3948 ValidationResult::with_errors(all_errors)
3949}
3950
3951#[cfg(test)]
3952mod tests;