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