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