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