1use std::collections::{HashMap, HashSet};
20use std::sync::atomic::{AtomicU64, Ordering};
21
22use fsqlite_ast::{
23 ColumnRef, Expr, FromClause, FunctionArgs, InSet, JoinClause, JoinConstraint, Literal,
24 QualifiedName, ResultColumn, SelectCore, SelectStatement, Statement, TableOrSubquery,
25 WithClause,
26};
27use fsqlite_types::TypeAffinity;
28
29static FSQLITE_SEMANTIC_ERRORS_TOTAL: AtomicU64 = AtomicU64::new(0);
35
36#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
38pub struct SemanticMetricsSnapshot {
39 pub fsqlite_semantic_errors_total: u64,
40}
41
42#[must_use]
44pub fn semantic_metrics_snapshot() -> SemanticMetricsSnapshot {
45 SemanticMetricsSnapshot {
46 fsqlite_semantic_errors_total: FSQLITE_SEMANTIC_ERRORS_TOTAL.load(Ordering::Relaxed),
47 }
48}
49
50pub fn reset_semantic_metrics() {
52 FSQLITE_SEMANTIC_ERRORS_TOTAL.store(0, Ordering::Relaxed);
53}
54
55#[derive(Debug, Clone, PartialEq, Eq)]
61pub struct ColumnDef {
62 pub name: String,
64 pub affinity: TypeAffinity,
66 pub is_ipk: bool,
68 pub not_null: bool,
70}
71
72#[derive(Debug, Clone)]
74pub struct TableDef {
75 pub name: String,
77 pub columns: Vec<ColumnDef>,
79 pub without_rowid: bool,
81 pub strict: bool,
83}
84
85impl TableDef {
86 #[must_use]
88 pub fn find_column(&self, name: &str) -> Option<&ColumnDef> {
89 self.columns
90 .iter()
91 .find(|c| c.name.eq_ignore_ascii_case(name))
92 }
93
94 #[must_use]
96 pub fn has_column(&self, name: &str) -> bool {
97 self.find_column(name).is_some()
98 }
99
100 #[must_use]
102 pub fn is_rowid_alias(&self, name: &str) -> bool {
103 if self.without_rowid {
104 return false;
105 }
106 if let Some(column) = self.find_column(name) {
107 return column.is_ipk;
108 }
109 is_hidden_rowid_alias_name(name)
110 }
111}
112
113fn is_hidden_rowid_alias_name(name: &str) -> bool {
114 matches!(
115 name.to_ascii_lowercase().as_str(),
116 "rowid" | "_rowid_" | "oid"
117 )
118}
119
120#[derive(Debug, Clone, Default)]
122pub struct Schema {
123 tables: HashMap<String, TableDef>,
125 namespaced_tables: HashMap<String, HashMap<String, TableDef>>,
127}
128
129impl Schema {
130 #[must_use]
132 pub fn new() -> Self {
133 Self::default()
134 }
135
136 pub fn add_table(&mut self, table: TableDef) {
138 self.tables.insert(table.name.to_ascii_lowercase(), table);
139 }
140
141 pub fn add_table_in_schema(&mut self, schema_name: &str, table: TableDef) {
143 if schema_name.eq_ignore_ascii_case("main") {
144 self.add_table(table);
145 return;
146 }
147
148 self.namespaced_tables
149 .entry(schema_name.to_ascii_lowercase())
150 .or_default()
151 .insert(table.name.to_ascii_lowercase(), table);
152 }
153
154 #[must_use]
156 pub fn find_table(&self, name: &str) -> Option<&TableDef> {
157 self.tables.get(&name.to_ascii_lowercase())
158 }
159
160 #[must_use]
162 pub fn find_table_in_schema(&self, schema: Option<&str>, name: &str) -> Option<&TableDef> {
163 match schema {
164 None => self.find_table(name),
165 Some(schema_name) if schema_name.eq_ignore_ascii_case("main") => self.find_table(name),
166 Some(schema_name) => self
167 .namespaced_tables
168 .get(&schema_name.to_ascii_lowercase())
169 .and_then(|tables| tables.get(&name.to_ascii_lowercase())),
170 }
171 }
172
173 #[must_use]
175 pub fn find_table_by_lookup_key(&self, lookup_key: &str) -> Option<&TableDef> {
176 if let Some((schema_name, table_name)) = lookup_key.split_once('\0') {
177 self.find_table_in_schema(Some(schema_name), table_name)
178 } else {
179 self.find_table(lookup_key)
180 }
181 }
182
183 #[must_use]
185 pub fn table_count(&self) -> usize {
186 self.tables.len()
187 + self
188 .namespaced_tables
189 .values()
190 .map(std::collections::HashMap::len)
191 .sum::<usize>()
192 }
193}
194
195fn table_lookup_key(name: &QualifiedName) -> String {
196 match name.schema.as_deref() {
197 None => name.name.to_ascii_lowercase(),
198 Some(schema_name) if schema_name.eq_ignore_ascii_case("main") => {
199 name.name.to_ascii_lowercase()
200 }
201 Some(schema_name) => format!(
202 "{}\0{}",
203 schema_name.to_ascii_lowercase(),
204 name.name.to_ascii_lowercase()
205 ),
206 }
207}
208
209fn lookup_key_table_name(lookup_key: &str) -> &str {
210 lookup_key
211 .split_once('\0')
212 .map_or(lookup_key, |(_, table_name)| table_name)
213}
214
215#[derive(Debug, Clone)]
221pub struct Scope {
222 aliases: HashMap<String, String>,
224 columns: HashMap<String, Option<HashSet<String>>>,
227 pub using_columns: HashSet<String>,
229 ctes: HashSet<String>,
231 qualified_only: HashSet<String>,
233 parent: Option<Box<Self>>,
235}
236
237impl Scope {
238 #[must_use]
240 pub fn root() -> Self {
241 Self {
242 aliases: HashMap::new(),
243 columns: HashMap::new(),
244 using_columns: HashSet::new(),
245 ctes: HashSet::new(),
246 qualified_only: HashSet::new(),
247 parent: None,
248 }
249 }
250
251 #[must_use]
253 pub fn child(parent: Self) -> Self {
254 Self {
255 aliases: HashMap::new(),
256 columns: HashMap::new(),
257 using_columns: HashSet::new(),
258 ctes: HashSet::new(),
259 qualified_only: HashSet::new(),
260 parent: Some(Box::new(parent)),
261 }
262 }
263
264 pub fn add_alias(&mut self, alias: &str, table_name: &str, columns: Option<HashSet<String>>) {
266 let key = alias.to_ascii_lowercase();
267 if self.aliases.contains_key(&key) {
268 self.aliases.insert(key.clone(), "<AMBIGUOUS>".to_owned());
269 self.columns.insert(key, None);
270 } else {
271 self.aliases.insert(key.clone(), table_name.to_owned());
272 self.columns.insert(key, columns);
273 }
274 }
275
276 pub fn add_qualified_only_alias(
278 &mut self,
279 alias: &str,
280 table_name: &str,
281 columns: Option<HashSet<String>>,
282 ) {
283 self.add_alias(alias, table_name, columns);
284 self.qualified_only.insert(alias.to_ascii_lowercase());
285 }
286
287 pub fn add_cte(&mut self, name: &str) {
289 self.ctes.insert(name.to_ascii_lowercase());
290 }
291
292 #[must_use]
294 pub fn has_cte(&self, name: &str) -> bool {
295 let key = name.to_ascii_lowercase();
296 if self.ctes.contains(&key) {
297 return true;
298 }
299 self.parent.as_ref().is_some_and(|p| p.has_cte(name))
300 }
301
302 #[must_use]
304 pub fn has_alias(&self, alias: &str) -> bool {
305 let key = alias.to_ascii_lowercase();
306 if self.aliases.contains_key(&key) {
307 return true;
308 }
309 self.parent.as_ref().is_some_and(|p| p.has_alias(alias))
310 }
311
312 #[must_use]
318 pub fn has_table_reference(&self, name: &QualifiedName) -> bool {
319 let target_lookup_key = table_lookup_key(name);
320 let target_name = name.name.to_ascii_lowercase();
321
322 if self.aliases.iter().any(|(alias, bound_name)| {
323 if name.schema.is_none() {
324 alias.eq_ignore_ascii_case(&target_name)
325 || lookup_key_table_name(bound_name).eq_ignore_ascii_case(&target_name)
326 } else {
327 bound_name.eq_ignore_ascii_case(&target_lookup_key)
328 }
329 }) {
330 return true;
331 }
332
333 self.parent
334 .as_ref()
335 .is_some_and(|parent| parent.has_table_reference(name))
336 }
337
338 #[must_use]
340 pub fn has_alias_local(&self, alias: &str) -> bool {
341 let key = alias.to_ascii_lowercase();
342 self.aliases.contains_key(&key)
343 }
344
345 #[must_use]
351 pub fn resolve_column(
352 &self,
353 schema: &Schema,
354 table_qualifier: Option<&str>,
355 column_name: &str,
356 ) -> ResolveResult {
357 let col_lower = column_name.to_ascii_lowercase();
358
359 if let Some(qualifier) = table_qualifier {
360 let key = qualifier.to_ascii_lowercase();
361 if self.aliases.get(&key).map(String::as_str) == Some("<AMBIGUOUS>") {
362 return ResolveResult::Ambiguous(vec![key]);
363 }
364 if let Some(cols) = self.columns.get(&key) {
365 if cols.as_ref().is_none_or(|c| c.contains(&col_lower)) {
366 return ResolveResult::Resolved(key);
367 }
368 if let Some(table_name) = self.aliases.get(&key) {
369 if let Some(table_def) = schema.find_table_by_lookup_key(table_name) {
370 if table_def.is_rowid_alias(&col_lower) {
371 return ResolveResult::Resolved(key);
372 }
373 }
374 }
375 return ResolveResult::ColumnNotFound;
376 }
377 if let Some(ref parent) = self.parent {
379 return parent.resolve_column(schema, table_qualifier, column_name);
380 }
381 return ResolveResult::TableNotFound;
382 }
383
384 let mut known_matches = Vec::new();
386 let mut unknown_matches = Vec::new();
387
388 for (alias, cols) in &self.columns {
389 if self.qualified_only.contains(alias) {
390 continue;
391 }
392 if self.aliases.get(alias).map(String::as_str) == Some("<AMBIGUOUS>") {
393 continue; }
395 let is_match = match cols {
396 Some(c) => {
397 c.contains(&col_lower) || {
398 self.aliases
399 .get(alias)
400 .and_then(|t| schema.find_table_by_lookup_key(t))
401 .is_some_and(|td| td.is_rowid_alias(&col_lower))
402 }
403 }
404 None => true,
405 };
406 if is_match {
407 if cols.is_some() {
408 known_matches.push(alias.clone());
409 } else {
410 unknown_matches.push(alias.clone());
411 }
412 }
413 }
414
415 match (known_matches.len(), unknown_matches.len()) {
416 (0, 0) => {
417 if let Some(ref parent) = self.parent {
419 return parent.resolve_column(schema, None, column_name);
420 }
421 ResolveResult::ColumnNotFound
422 }
423 (1, 0) => ResolveResult::Resolved(known_matches.into_iter().next().unwrap_or_default()),
424 (0, 1) => {
425 ResolveResult::Resolved(unknown_matches.into_iter().next().unwrap_or_default())
426 }
427 _ => {
428 let mut all_matches = known_matches;
429 all_matches.extend(unknown_matches);
430 all_matches.sort();
431 if self.using_columns.contains(&col_lower) {
432 ResolveResult::Resolved(all_matches.into_iter().next().unwrap_or_default())
434 } else if all_matches.contains(&"<output>".to_owned()) {
435 ResolveResult::Resolved("<output>".to_owned())
436 } else {
437 ResolveResult::Ambiguous(all_matches)
438 }
439 }
440 }
441 }
442
443 #[must_use]
445 pub fn alias_count(&self) -> usize {
446 self.aliases.len()
447 }
448
449 #[must_use]
452 pub fn known_local_column_sets(&self) -> Vec<&HashSet<String>> {
453 self.columns
454 .values()
455 .filter_map(|opt| opt.as_ref())
456 .collect()
457 }
458
459 #[must_use]
461 pub fn columns_for_alias(&self, alias: &str) -> Option<&HashSet<String>> {
462 self.columns
463 .get(&alias.to_ascii_lowercase())
464 .and_then(|opt| opt.as_ref())
465 }
466}
467
468#[derive(Debug, Clone, PartialEq, Eq)]
470pub enum ResolveResult {
471 Resolved(String),
473 TableNotFound,
475 ColumnNotFound,
477 Ambiguous(Vec<String>),
479}
480
481#[derive(Debug, Clone, PartialEq, Eq)]
487pub struct SemanticError {
488 pub kind: SemanticErrorKind,
490 pub message: String,
492}
493
494#[derive(Debug, Clone, PartialEq, Eq)]
496pub enum SemanticErrorKind {
497 UnresolvedColumn {
499 table: Option<String>,
500 column: String,
501 },
502 AmbiguousColumn {
504 column: String,
505 candidates: Vec<String>,
506 },
507 UnresolvedTable { name: String },
509 DuplicateAlias { alias: String },
511 FunctionArityMismatch {
513 function: String,
514 expected: FunctionArity,
515 actual: usize,
516 },
517 NoTablesSpecifiedForStar,
519 ImplicitTypeCoercion {
521 from: TypeAffinity,
522 to: TypeAffinity,
523 context: String,
524 },
525 InvalidFunctionArgument { message: String },
529}
530
531#[derive(Debug, Clone, PartialEq, Eq)]
533pub enum FunctionArity {
534 Exact(usize),
536 Range(usize, usize),
538 Variadic,
540 VariadicMin(usize),
542}
543
544impl std::fmt::Display for SemanticError {
545 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
546 write!(f, "{}", self.message)
547 }
548}
549
550pub struct Resolver<'a> {
559 schema: &'a Schema,
560 errors: Vec<SemanticError>,
561 tables_resolved: u64,
562 columns_bound: u64,
563}
564
565impl<'a> Resolver<'a> {
566 #[must_use]
568 pub fn new(schema: &'a Schema) -> Self {
569 Self {
570 schema,
571 errors: Vec::new(),
572 tables_resolved: 0,
573 columns_bound: 0,
574 }
575 }
576
577 pub fn resolve_statement(&mut self, stmt: &Statement) -> Vec<SemanticError> {
581 let span = tracing::debug_span!(
582 target: "fsqlite.parse",
583 "semantic_analysis",
584 tables_resolved = tracing::field::Empty,
585 columns_bound = tracing::field::Empty,
586 errors = tracing::field::Empty,
587 );
588 let _guard = span.enter();
589
590 self.errors.clear();
591 self.tables_resolved = 0;
592 self.columns_bound = 0;
593
594 let mut scope = Scope::root();
595 self.resolve_stmt_inner(stmt, &mut scope);
596
597 span.record("tables_resolved", self.tables_resolved);
598 span.record("columns_bound", self.columns_bound);
599 span.record("errors", self.errors.len() as u64);
600
601 if !self.errors.is_empty() {
603 FSQLITE_SEMANTIC_ERRORS_TOTAL.fetch_add(self.errors.len() as u64, Ordering::Relaxed);
604 }
605
606 self.errors.clone()
607 }
608
609 fn resolve_stmt_inner(&mut self, stmt: &Statement, scope: &mut Scope) {
610 match stmt {
611 Statement::Select(select) => self.resolve_select(select, scope),
612 Statement::Insert(insert) => {
613 if let Some(ref with) = insert.with {
615 self.resolve_with_clause(with, scope);
616 }
617
618 match &insert.source {
621 fsqlite_ast::InsertSource::Values(rows) => {
622 for row in rows {
623 for expr in row {
624 self.resolve_expr(expr, scope);
625 }
626 }
627 }
628 fsqlite_ast::InsertSource::Select(select) => {
629 let mut source_scope = scope.clone();
630 self.resolve_select(select, &mut source_scope);
631 }
632 fsqlite_ast::InsertSource::DefaultValues => {}
633 }
634
635 self.bind_table_to_scope(&insert.table, None, scope);
637
638 let mut target_scope = Scope::root();
640 if insert.table.schema.is_none() && scope.has_cte(&insert.table.name) {
641 target_scope.add_alias(&insert.table.name, &insert.table.name, None);
642 } else if let Some(table_def) = self
643 .schema
644 .find_table_in_schema(insert.table.schema.as_deref(), &insert.table.name)
645 {
646 let col_set: HashSet<String> = table_def
647 .columns
648 .iter()
649 .map(|c| c.name.to_ascii_lowercase())
650 .collect();
651 target_scope.add_alias(
652 &insert.table.name,
653 &table_lookup_key(&insert.table),
654 Some(col_set),
655 );
656 }
657
658 for col in &insert.columns {
659 self.resolve_unqualified_column(col, &target_scope, false);
660 }
661
662 for upsert in &insert.upsert {
664 if let Some(target) = &upsert.target {
665 for col in &target.columns {
666 self.resolve_expr(&col.expr, scope);
667 }
668 if let Some(where_clause) = &target.where_clause {
669 self.resolve_expr(where_clause, scope);
670 }
671 }
672 match &upsert.action {
673 fsqlite_ast::UpsertAction::Update {
674 assignments,
675 where_clause,
676 } => {
677 let mut upsert_scope = Scope::child(scope.clone());
678 let alias_name = insert.alias.as_deref().unwrap_or(&insert.table.name);
679 let target_lookup_key = table_lookup_key(&insert.table);
680 if let Some(table_def) = self.schema.find_table_in_schema(
681 insert.table.schema.as_deref(),
682 &insert.table.name,
683 ) {
684 let col_set: HashSet<String> = table_def
685 .columns
686 .iter()
687 .map(|c| c.name.to_ascii_lowercase())
688 .collect();
689 upsert_scope.add_qualified_only_alias(
690 "excluded",
691 &target_lookup_key,
692 Some(col_set.clone()),
693 );
694 upsert_scope.add_alias(
695 alias_name,
696 &target_lookup_key,
697 Some(col_set),
698 );
699 } else {
700 upsert_scope.add_qualified_only_alias("excluded", "<pseudo>", None);
701 upsert_scope.add_alias(alias_name, "<pseudo>", None);
702 }
703
704 for assignment in assignments {
705 match &assignment.target {
706 fsqlite_ast::AssignmentTarget::Column(col) => {
707 self.resolve_unqualified_column(col, &target_scope, false);
708 }
709 fsqlite_ast::AssignmentTarget::ColumnList(cols) => {
710 for col in cols {
711 self.resolve_unqualified_column(
712 col,
713 &target_scope,
714 false,
715 );
716 }
717 }
718 }
719 self.resolve_expr(&assignment.value, &upsert_scope);
720 }
721 if let Some(w) = where_clause {
722 self.resolve_expr(w, &upsert_scope);
723 }
724 }
725 fsqlite_ast::UpsertAction::Nothing => {}
726 }
727 }
728 for ret in &insert.returning {
729 self.resolve_result_column(ret, scope);
730 }
731 }
732 Statement::Update(update) => {
733 if let Some(ref with) = update.with {
735 self.resolve_with_clause(with, scope);
736 }
737
738 let limit_scope = scope.clone();
740
741 self.bind_table_to_scope(&update.table.name, update.table.alias.as_deref(), scope);
742
743 let mut target_scope = Scope::root();
745 self.bind_table_to_scope(
746 &update.table.name,
747 update.table.alias.as_deref(),
748 &mut target_scope,
749 );
750
751 let returning_scope = scope.clone();
754
755 for assignment in &update.assignments {
756 match &assignment.target {
757 fsqlite_ast::AssignmentTarget::Column(col) => {
758 self.resolve_unqualified_column(col, &target_scope, false);
759 }
760 fsqlite_ast::AssignmentTarget::ColumnList(cols) => {
761 for col in cols {
762 self.resolve_unqualified_column(col, &target_scope, false);
763 }
764 }
765 }
766 }
767 if let Some(from) = &update.from {
768 self.resolve_from(from, scope);
769 }
770 for assignment in &update.assignments {
771 self.resolve_expr(&assignment.value, scope);
772 }
773 if let Some(where_clause) = &update.where_clause {
774 self.resolve_expr(where_clause, scope);
775 }
776 for ret in &update.returning {
777 self.resolve_result_column(ret, &returning_scope);
778 }
779 for term in &update.order_by {
780 self.resolve_expr(&term.expr, scope);
781 }
782 if let Some(limit) = &update.limit {
783 self.resolve_expr(&limit.limit, &limit_scope);
784 if let Some(offset) = &limit.offset {
785 self.resolve_expr(offset, &limit_scope);
786 }
787 }
788 }
789 Statement::Delete(delete) => {
790 if let Some(ref with) = delete.with {
792 self.resolve_with_clause(with, scope);
793 }
794
795 let limit_scope = scope.clone();
797
798 self.bind_table_to_scope(&delete.table.name, delete.table.alias.as_deref(), scope);
799 if let Some(where_clause) = &delete.where_clause {
800 self.resolve_expr(where_clause, scope);
801 }
802 for ret in &delete.returning {
803 self.resolve_result_column(ret, scope);
804 }
805 for term in &delete.order_by {
806 self.resolve_expr(&term.expr, scope);
807 }
808 if let Some(limit) = &delete.limit {
809 self.resolve_expr(&limit.limit, &limit_scope);
810 if let Some(offset) = &limit.offset {
811 self.resolve_expr(offset, &limit_scope);
812 }
813 }
814 }
815 _ => {}
817 }
818 }
819
820 fn resolve_with_clause(&mut self, with: &WithClause, scope: &mut Scope) {
821 if with.recursive {
822 for cte in &with.ctes {
824 scope.add_cte(&cte.name);
825 }
826 for cte in &with.ctes {
827 let mut cte_scope = scope.clone();
828 self.resolve_select(&cte.query, &mut cte_scope);
829 }
830 } else {
831 for cte in &with.ctes {
833 let mut cte_scope = scope.clone();
834 self.resolve_select(&cte.query, &mut cte_scope);
835 scope.add_cte(&cte.name);
837 }
838 }
839 }
840
841 fn compound_order_by_matches_output_expr(select: &SelectStatement, order_expr: &Expr) -> bool {
845 if select.body.compounds.is_empty() {
846 return false;
847 }
848
849 std::iter::once(&select.body.select)
850 .chain(select.body.compounds.iter().map(|(_, core)| core))
851 .filter_map(|core| match core {
852 SelectCore::Select { columns, .. } => Some(columns.iter()),
853 _ => None,
854 })
855 .flatten()
856 .any(|column| match column {
857 ResultColumn::Expr { expr, .. } => expr == order_expr,
858 _ => false,
859 })
860 }
861
862 fn resolve_select(&mut self, select: &SelectStatement, scope: &mut Scope) {
863 if let Some(ref with) = select.with {
865 self.resolve_with_clause(with, scope);
866 }
867
868 let mut first_core_scope = scope.clone();
870 self.resolve_select_core(&select.body.select, &mut first_core_scope);
871
872 for (_op, core) in &select.body.compounds {
874 let mut comp_scope = scope.clone();
875 self.resolve_select_core(core, &mut comp_scope);
876 }
877
878 let mut order_by_scope = if select.body.compounds.is_empty() {
880 first_core_scope.clone()
881 } else {
882 scope.clone() };
884
885 let mut output_cols = HashSet::new();
886 for core in std::iter::once(&select.body.select)
887 .chain(select.body.compounds.iter().map(|(_, core)| core))
888 {
889 if let SelectCore::Select { columns, .. } = core {
890 for col in columns {
891 match col {
892 ResultColumn::Expr {
893 alias: Some(alias_id),
894 ..
895 } => {
896 output_cols.insert(alias_id.to_ascii_lowercase());
897 }
898 ResultColumn::Expr {
899 expr: Expr::Column(col_ref, _),
900 ..
901 } => {
902 output_cols.insert(col_ref.column.to_ascii_lowercase());
903 }
904 _ => {}
905 }
906 }
907 }
908 }
909 if !output_cols.is_empty() {
910 order_by_scope.add_alias("<output>", "<output>", Some(output_cols));
912 }
913
914 for term in &select.order_by {
915 if Self::compound_order_by_matches_output_expr(select, &term.expr) {
916 continue;
917 }
918 self.resolve_expr(&term.expr, &order_by_scope);
919 }
920
921 if let Some(limit) = &select.limit {
923 self.resolve_expr(&limit.limit, scope);
924 if let Some(offset) = &limit.offset {
925 self.resolve_expr(offset, scope);
926 }
927 }
928 }
929
930 fn resolve_select_core(&mut self, core: &SelectCore, scope: &mut Scope) {
931 match core {
932 SelectCore::Select {
933 columns,
934 from,
935 where_clause,
936 group_by,
937 having,
938 windows,
939 ..
940 } => {
941 if let Some(from) = from {
943 self.resolve_from(from, scope);
944 }
945
946 for col in columns {
948 self.resolve_result_column(col, scope);
949 }
950
951 if let Some(where_expr) = where_clause {
953 self.resolve_expr(where_expr, scope);
954 }
955
956 let mut post_select_scope = scope.clone();
958 let mut output_cols = HashSet::new();
959 for col in columns {
960 if let ResultColumn::Expr {
961 alias: Some(alias_id),
962 ..
963 } = col
964 {
965 output_cols.insert(alias_id.to_ascii_lowercase());
966 } else if let ResultColumn::Expr {
967 expr: Expr::Column(col_ref, _),
968 ..
969 } = col
970 {
971 output_cols.insert(col_ref.column.to_ascii_lowercase());
972 }
973 }
974 if !output_cols.is_empty() {
975 post_select_scope.add_alias("<output>", "<output>", Some(output_cols));
976 } else {
977 post_select_scope.add_alias("<output>", "<output>", None);
978 }
979
980 for expr in group_by {
981 self.resolve_expr(expr, &post_select_scope);
982 }
983 if let Some(having) = having {
984 self.resolve_expr(having, &post_select_scope);
985 }
986 for window in windows {
987 for part in &window.spec.partition_by {
988 self.resolve_expr(part, &post_select_scope);
989 }
990 for order in &window.spec.order_by {
991 self.resolve_expr(&order.expr, &post_select_scope);
992 }
993 }
994 }
995 SelectCore::Values(rows) => {
996 for row in rows {
997 for expr in row {
998 self.resolve_expr(expr, scope);
999 }
1000 }
1001 }
1002 }
1003 }
1004
1005 fn resolve_from(&mut self, from: &FromClause, scope: &mut Scope) {
1006 self.resolve_table_or_subquery(&from.source, scope);
1007
1008 for join in &from.joins {
1009 self.resolve_join(join, scope);
1010 }
1011 }
1012
1013 fn resolve_table_or_subquery(&mut self, tos: &TableOrSubquery, scope: &mut Scope) {
1014 match tos {
1015 TableOrSubquery::Table { name, alias, .. } => {
1016 let table_name = &name.name;
1017 let alias_name = alias.as_deref().unwrap_or(table_name);
1018
1019 if scope.has_alias_local(alias_name) {
1021 self.push_error(SemanticErrorKind::DuplicateAlias {
1022 alias: alias_name.to_owned(),
1023 });
1024 }
1025
1026 if name.schema.is_none() && scope.has_cte(table_name) {
1028 scope.add_alias(alias_name, table_name, None);
1030 self.tables_resolved += 1;
1031 } else if let Some(table_def) = self
1032 .schema
1033 .find_table_in_schema(name.schema.as_deref(), table_name)
1034 {
1035 let col_set: HashSet<String> = table_def
1036 .columns
1037 .iter()
1038 .map(|c| c.name.to_ascii_lowercase())
1039 .collect();
1040 scope.add_alias(alias_name, &table_lookup_key(name), Some(col_set));
1041 self.tables_resolved += 1;
1042 } else {
1043 self.push_error(SemanticErrorKind::UnresolvedTable {
1044 name: name.to_string(),
1045 });
1046 }
1047 }
1048 TableOrSubquery::Subquery { query, alias, .. } => {
1049 let mut child = Scope::child(scope.clone());
1051 self.resolve_select(query, &mut child);
1052
1053 let alias_name = if let Some(a) = alias {
1054 a.clone()
1055 } else {
1056 format!("<subquery_{}>", self.tables_resolved)
1057 };
1058
1059 if !alias_name.starts_with("<subquery_") && scope.has_alias_local(&alias_name) {
1060 self.push_error(SemanticErrorKind::DuplicateAlias {
1061 alias: alias_name.clone(),
1062 });
1063 }
1064
1065 let mut output_cols = HashSet::new();
1066 let mut is_complete = true;
1067 if let SelectCore::Select { columns, .. } = &query.body.select {
1068 for col in columns {
1069 match col {
1070 ResultColumn::Expr {
1071 alias: Some(alias_id),
1072 ..
1073 } => {
1074 output_cols.insert(alias_id.to_ascii_lowercase());
1075 }
1076 ResultColumn::Expr {
1077 expr: Expr::Column(col_ref, _),
1078 ..
1079 } => {
1080 output_cols.insert(col_ref.column.to_ascii_lowercase());
1081 }
1082 ResultColumn::Star | ResultColumn::TableStar(_) => {
1083 is_complete = false;
1084 }
1085 _ => {}
1086 }
1087 }
1088 } else {
1089 is_complete = false;
1090 }
1091
1092 if is_complete {
1093 scope.add_alias(&alias_name, "<subquery>", Some(output_cols));
1094 } else {
1095 scope.add_alias(&alias_name, "<subquery>", None);
1096 }
1097
1098 self.tables_resolved += 1;
1099 }
1100 TableOrSubquery::TableFunction {
1101 name, args, alias, ..
1102 } => {
1103 for arg in args {
1104 self.resolve_expr(arg, scope);
1105 }
1106
1107 let alias_name = alias.as_deref().unwrap_or(name);
1108
1109 if scope.has_alias_local(alias_name) {
1110 self.push_error(SemanticErrorKind::DuplicateAlias {
1111 alias: alias_name.to_owned(),
1112 });
1113 }
1114
1115 scope.add_alias(alias_name, name, None);
1116 self.tables_resolved += 1;
1117 }
1118 TableOrSubquery::ParenJoin(inner_from) => {
1119 self.resolve_from(inner_from, scope);
1120 }
1121 }
1122 }
1123
1124 fn resolve_join(&mut self, join: &JoinClause, scope: &mut Scope) {
1125 let pre_join_columns: Vec<HashSet<String>> = scope
1128 .known_local_column_sets()
1129 .into_iter()
1130 .cloned()
1131 .collect();
1132 let pre_join_aliases: HashSet<String> = scope.aliases.keys().cloned().collect();
1133
1134 self.resolve_table_or_subquery(&join.table, scope);
1135
1136 if join.join_type.natural && join.constraint.is_none() {
1137 let mut to_insert = Vec::new();
1140 for (alias, cols_opt) in &scope.columns {
1141 if !pre_join_aliases.contains(alias) {
1142 if let Some(new_cols) = cols_opt {
1143 for col_name in new_cols {
1144 if pre_join_columns.iter().any(|cs| cs.contains(col_name)) {
1145 to_insert.push(col_name.clone());
1146 }
1147 }
1148 }
1149 }
1150 }
1151 for col_name in to_insert {
1152 scope.using_columns.insert(col_name);
1153 }
1154 }
1155
1156 if let Some(ref constraint) = join.constraint {
1157 match constraint {
1158 JoinConstraint::On(expr) => self.resolve_expr(expr, scope),
1159 JoinConstraint::Using(cols) => {
1160 for col in cols {
1161 let col_lower = col.to_ascii_lowercase();
1162 scope.using_columns.insert(col_lower.clone());
1163
1164 let in_left = pre_join_columns.iter().any(|cs| cs.contains(&col_lower));
1166 let mut in_right = false;
1168 for (alias, cols_opt) in &scope.columns {
1169 if !pre_join_aliases.contains(alias) {
1170 if let Some(new_cols) = cols_opt {
1171 if new_cols.contains(&col_lower) {
1172 in_right = true;
1173 break;
1174 }
1175 } else {
1176 in_right = true;
1178 break;
1179 }
1180 }
1181 }
1182
1183 let left_has_unknown = scope.columns.iter().any(|(alias, cols_opt)| {
1185 pre_join_aliases.contains(alias) && cols_opt.is_none()
1186 });
1187
1188 if (!in_left && !left_has_unknown) || !in_right {
1189 self.push_error(SemanticErrorKind::UnresolvedColumn {
1190 table: None,
1191 column: col.clone(),
1192 });
1193 }
1194
1195 self.resolve_unqualified_column(col, scope, true);
1196 }
1197 }
1198 }
1199 }
1200 }
1201
1202 fn resolve_result_column(&mut self, col: &ResultColumn, scope: &Scope) {
1203 match col {
1204 ResultColumn::Star => {
1205 if scope.alias_count() == 0
1209 && !self
1210 .errors
1211 .iter()
1212 .any(|e| matches!(e.kind, SemanticErrorKind::UnresolvedTable { .. }))
1213 {
1214 self.push_error(SemanticErrorKind::NoTablesSpecifiedForStar);
1215 }
1216 }
1217 ResultColumn::TableStar(table_name) => {
1218 if !scope.has_table_reference(table_name) {
1219 self.push_error(SemanticErrorKind::UnresolvedTable {
1220 name: table_name.to_string(),
1221 });
1222 }
1223 }
1224 ResultColumn::Expr { expr, .. } => {
1225 self.resolve_expr(expr, scope);
1226 }
1227 }
1228 }
1229
1230 #[allow(clippy::too_many_lines)]
1231 fn resolve_expr(&mut self, expr: &Expr, scope: &Scope) {
1232 match expr {
1233 Expr::Column(col_ref, _span) => {
1234 self.resolve_column_ref(col_ref, scope);
1235 }
1236 Expr::BinaryOp { left, right, .. } => {
1237 self.resolve_expr(left, scope);
1238 self.resolve_expr(right, scope);
1239 }
1240 Expr::UnaryOp { expr: inner, .. }
1241 | Expr::Cast { expr: inner, .. }
1242 | Expr::Collate { expr: inner, .. }
1243 | Expr::IsNull { expr: inner, .. } => {
1244 self.resolve_expr(inner, scope);
1245 }
1246 Expr::Between {
1247 expr: inner,
1248 low,
1249 high,
1250 ..
1251 } => {
1252 self.resolve_expr(inner, scope);
1253 self.resolve_expr(low, scope);
1254 self.resolve_expr(high, scope);
1255 }
1256 Expr::In {
1257 expr: inner, set, ..
1258 } => {
1259 self.resolve_expr(inner, scope);
1260 match set {
1261 InSet::List(items) => {
1262 for item in items {
1263 self.resolve_expr(item, scope);
1264 }
1265 }
1266 InSet::Subquery(select) => {
1267 let mut child = Scope::child(scope.clone());
1268 self.resolve_select(select, &mut child);
1269 }
1270 InSet::Table(name) => self.resolve_table_name(name, scope),
1271 }
1272 }
1273 Expr::Like {
1274 expr: inner,
1275 pattern,
1276 escape,
1277 op,
1278 ..
1279 } => {
1280 self.resolve_expr(inner, scope);
1281 self.resolve_expr(pattern, scope);
1282 if let Some(esc) = escape {
1283 if *op != fsqlite_ast::LikeOp::Like {
1284 self.push_error(SemanticErrorKind::FunctionArityMismatch {
1286 function: match op {
1287 fsqlite_ast::LikeOp::Like => "LIKE",
1288 fsqlite_ast::LikeOp::Glob => "GLOB",
1289 fsqlite_ast::LikeOp::Match => "MATCH",
1290 fsqlite_ast::LikeOp::Regexp => "REGEXP",
1291 }
1292 .to_owned(),
1293 expected: FunctionArity::Exact(2),
1294 actual: 3,
1295 });
1296 }
1297 self.resolve_expr(esc, scope);
1298 }
1299 }
1300 Expr::Subquery(select, _)
1301 | Expr::Exists {
1302 subquery: select, ..
1303 } => {
1304 let mut child = Scope::child(scope.clone());
1305 self.resolve_select(select, &mut child);
1306 }
1307 Expr::FunctionCall {
1308 name,
1309 args,
1310 filter,
1311 over,
1312 ..
1313 } => {
1314 self.resolve_function(name, args, scope);
1315 if let Some(filter) = filter {
1316 self.resolve_expr(filter, scope);
1317 }
1318 if let Some(window_spec) = over {
1319 for expr in &window_spec.partition_by {
1320 self.resolve_expr(expr, scope);
1321 }
1322 for term in &window_spec.order_by {
1323 self.resolve_expr(&term.expr, scope);
1324 }
1325 if let Some(frame) = &window_spec.frame {
1326 match &frame.start {
1327 fsqlite_ast::FrameBound::Preceding(expr)
1328 | fsqlite_ast::FrameBound::Following(expr) => {
1329 self.resolve_expr(expr, scope);
1330 }
1331 _ => {}
1332 }
1333 if let Some(
1334 fsqlite_ast::FrameBound::Preceding(expr)
1335 | fsqlite_ast::FrameBound::Following(expr),
1336 ) = &frame.end
1337 {
1338 self.resolve_expr(expr, scope);
1339 }
1340 }
1341 }
1342 }
1343 Expr::Case {
1344 operand,
1345 whens,
1346 else_expr,
1347 ..
1348 } => {
1349 if let Some(op) = operand {
1350 self.resolve_expr(op, scope);
1351 }
1352 for (when_expr, then_expr) in whens {
1353 self.resolve_expr(when_expr, scope);
1354 self.resolve_expr(then_expr, scope);
1355 }
1356 if let Some(else_e) = else_expr {
1357 self.resolve_expr(else_e, scope);
1358 }
1359 }
1360 Expr::JsonAccess {
1361 expr: inner, path, ..
1362 } => {
1363 self.resolve_expr(inner, scope);
1364 self.resolve_expr(path, scope);
1365 }
1366 Expr::RowValue(exprs, _) => {
1367 for e in exprs {
1368 self.resolve_expr(e, scope);
1369 }
1370 }
1371 Expr::Literal(_, _) | Expr::Placeholder(_, _) | Expr::Raise { .. } => {}
1373 }
1374 }
1375
1376 fn resolve_column_ref(&mut self, col_ref: &ColumnRef, scope: &Scope) {
1377 let result = scope.resolve_column(self.schema, col_ref.table.as_deref(), &col_ref.column);
1378 match result {
1379 ResolveResult::Resolved(_) => {
1380 self.columns_bound += 1;
1381 }
1382 ResolveResult::TableNotFound => {
1383 tracing::error!(
1384 target: "fsqlite.parse",
1385 table = ?col_ref.table,
1386 column = %col_ref.column,
1387 "unresolvable table reference"
1388 );
1389 self.push_error(SemanticErrorKind::UnresolvedColumn {
1390 table: col_ref.table.as_ref().map(ToString::to_string),
1391 column: col_ref.column.to_string(),
1392 });
1393 }
1394 ResolveResult::ColumnNotFound => {
1395 tracing::error!(
1396 target: "fsqlite.parse",
1397 table = ?col_ref.table,
1398 column = %col_ref.column,
1399 "unresolvable column reference"
1400 );
1401 self.push_error(SemanticErrorKind::UnresolvedColumn {
1402 table: col_ref.table.as_ref().map(ToString::to_string),
1403 column: col_ref.column.to_string(),
1404 });
1405 }
1406 ResolveResult::Ambiguous(candidates) => {
1407 tracing::error!(
1408 target: "fsqlite.parse",
1409 column = %col_ref.column,
1410 candidates = ?candidates,
1411 "ambiguous column reference"
1412 );
1413 self.push_error(SemanticErrorKind::AmbiguousColumn {
1414 column: col_ref.column.to_string(),
1415 candidates,
1416 });
1417 }
1418 }
1419 }
1420
1421 fn resolve_unqualified_column(&mut self, name: &str, scope: &Scope, is_using_clause: bool) {
1422 let result = scope.resolve_column(self.schema, None, name);
1423 match result {
1424 ResolveResult::Resolved(_) => {
1425 self.columns_bound += 1;
1426 }
1427 ResolveResult::Ambiguous(candidates) => {
1428 if is_using_clause {
1429 self.columns_bound += 1;
1430 } else {
1431 self.push_error(SemanticErrorKind::AmbiguousColumn {
1432 column: name.to_owned(),
1433 candidates,
1434 });
1435 }
1436 }
1437 ResolveResult::ColumnNotFound | ResolveResult::TableNotFound => {
1438 self.push_error(SemanticErrorKind::UnresolvedColumn {
1439 table: None,
1440 column: name.to_owned(),
1441 });
1442 }
1443 }
1444 }
1445
1446 fn bind_table_to_scope(
1447 &mut self,
1448 name: &QualifiedName,
1449 alias: Option<&str>,
1450 scope: &mut Scope,
1451 ) {
1452 let alias_name = alias.unwrap_or(&name.name);
1453 if name.schema.is_none() && scope.has_cte(&name.name) {
1454 scope.add_alias(alias_name, &name.name, None);
1455 self.tables_resolved += 1;
1456 } else if let Some(table_def) = self
1457 .schema
1458 .find_table_in_schema(name.schema.as_deref(), &name.name)
1459 {
1460 let col_set: HashSet<String> = table_def
1461 .columns
1462 .iter()
1463 .map(|c| c.name.to_ascii_lowercase())
1464 .collect();
1465 scope.add_alias(alias_name, &table_lookup_key(name), Some(col_set));
1466 self.tables_resolved += 1;
1467 } else {
1468 self.push_error(SemanticErrorKind::UnresolvedTable {
1469 name: name.to_string(),
1470 });
1471 }
1472 }
1473
1474 fn resolve_table_name(&mut self, name: &QualifiedName, _scope: &Scope) {
1475 if self
1476 .schema
1477 .find_table_in_schema(name.schema.as_deref(), &name.name)
1478 .is_some()
1479 {
1480 self.tables_resolved += 1;
1481 } else {
1482 self.push_error(SemanticErrorKind::UnresolvedTable {
1483 name: name.to_string(),
1484 });
1485 }
1486 }
1487
1488 fn resolve_function(&mut self, name: &str, args: &FunctionArgs, scope: &Scope) {
1489 let actual = match args {
1491 FunctionArgs::Star => {
1492 if !name.eq_ignore_ascii_case("count") {
1493 let expected = known_function_arity(name).unwrap_or(FunctionArity::Range(0, 1));
1494 self.push_error(SemanticErrorKind::FunctionArityMismatch {
1495 function: name.to_owned(),
1496 expected,
1497 actual: 1,
1498 });
1499 }
1500 1 }
1502 FunctionArgs::List(list) => {
1503 for arg in list {
1504 self.resolve_expr(arg, scope);
1505 }
1506 list.len()
1507 }
1508 };
1509
1510 if let Some(expected) = known_function_arity(name) {
1512 let valid = match &expected {
1513 FunctionArity::Exact(n) => actual == *n,
1514 FunctionArity::Range(lo, hi) => actual >= *lo && actual <= *hi,
1515 FunctionArity::Variadic => true,
1516 FunctionArity::VariadicMin(min) => actual >= *min,
1517 };
1518 if !valid {
1519 self.push_error(SemanticErrorKind::FunctionArityMismatch {
1520 function: name.to_owned(),
1521 expected,
1522 actual,
1523 });
1524 }
1525 }
1526
1527 if name.eq_ignore_ascii_case("likelihood") {
1532 if let FunctionArgs::List(list) = args {
1533 if list.len() == 2 {
1534 let is_valid_probability = matches!(
1535 &list[1],
1536 Expr::Literal(Literal::Float(p), _) if (0.0..=1.0).contains(p)
1537 );
1538 if !is_valid_probability {
1539 self.push_error(SemanticErrorKind::InvalidFunctionArgument {
1540 message:
1541 "second argument to likelihood() must be a constant between 0.0 and 1.0"
1542 .to_owned(),
1543 });
1544 }
1545 }
1546 }
1547 }
1548 }
1549
1550 fn push_error(&mut self, kind: SemanticErrorKind) {
1551 let message = match &kind {
1552 SemanticErrorKind::UnresolvedColumn { table, column } => {
1553 if let Some(t) = table {
1554 format!("no such column: {t}.{column}")
1555 } else {
1556 format!("no such column: {column}")
1557 }
1558 }
1559 SemanticErrorKind::AmbiguousColumn {
1560 column, candidates, ..
1561 } => {
1562 format!(
1563 "ambiguous column name: {column} (candidates: {})",
1564 candidates.join(", ")
1565 )
1566 }
1567 SemanticErrorKind::UnresolvedTable { name } => {
1568 format!("no such table: {name}")
1569 }
1570 SemanticErrorKind::DuplicateAlias { alias } => {
1571 format!("duplicate alias: {alias}")
1572 }
1573 SemanticErrorKind::FunctionArityMismatch {
1574 function,
1575 expected,
1576 actual,
1577 } => {
1578 format!(
1579 "wrong number of arguments to function {function}: expected {expected:?}, got {actual}"
1580 )
1581 }
1582 SemanticErrorKind::NoTablesSpecifiedForStar => "no tables specified".to_string(),
1583 SemanticErrorKind::ImplicitTypeCoercion {
1584 from, to, context, ..
1585 } => {
1586 format!("implicit type coercion from {from:?} to {to:?} in {context}")
1587 }
1588 SemanticErrorKind::InvalidFunctionArgument { message } => message.clone(),
1589 };
1590
1591 self.errors.push(SemanticError { kind, message });
1592 }
1593}
1594
1595#[must_use]
1601fn known_function_arity(name: &str) -> Option<FunctionArity> {
1602 match name.to_ascii_lowercase().as_str() {
1603 "random" | "changes" | "last_insert_rowid" | "total_changes" => {
1604 Some(FunctionArity::Exact(0))
1605 }
1606 "sum" | "total" | "avg" | "abs" | "hex" | "length" | "lower" | "upper" | "typeof"
1608 | "unicode" | "quote" | "zeroblob" | "soundex" | "likely" | "unlikely" | "randomblob" => {
1609 Some(FunctionArity::Exact(1))
1610 }
1611 "ifnull" | "nullif" | "instr" | "glob" | "likelihood" => Some(FunctionArity::Exact(2)),
1612 "replace" => Some(FunctionArity::Exact(3)),
1613 "count" => Some(FunctionArity::Range(0, 1)),
1614 "group_concat" | "trim" | "ltrim" | "rtrim" | "round" => Some(FunctionArity::Range(1, 2)),
1615 "substr" | "substring" | "like" | "iif" | "if" => Some(FunctionArity::Range(2, 3)),
1618 "coalesce" | "json_extract" => Some(FunctionArity::VariadicMin(2)),
1619 "json_remove" => Some(FunctionArity::VariadicMin(1)),
1620 "json_insert" | "json_replace" | "json_set" => Some(FunctionArity::VariadicMin(3)),
1621 "min" | "max" | "printf" | "format" | "strftime" | "json" | "json_type" | "json_valid" => {
1623 Some(FunctionArity::VariadicMin(1))
1624 }
1625 "date" | "time" | "datetime" | "julianday" | "unixepoch" => {
1626 Some(FunctionArity::VariadicMin(0))
1627 }
1628 "char" | "json_array" | "json_object" => Some(FunctionArity::Variadic),
1629
1630 _ => None, }
1632}
1633
1634#[cfg(test)]
1639#[path = "semantic_test.rs"]
1640mod semantic_test;
1641
1642#[cfg(test)]
1643mod tests {
1644 use super::*;
1645 use crate::parser::Parser;
1646
1647 fn make_schema() -> Schema {
1648 let mut schema = Schema::new();
1649 schema.add_table(TableDef {
1650 name: "users".to_owned(),
1651 columns: vec![
1652 ColumnDef {
1653 name: "id".to_owned(),
1654 affinity: TypeAffinity::Integer,
1655 is_ipk: true,
1656 not_null: true,
1657 },
1658 ColumnDef {
1659 name: "name".to_owned(),
1660 affinity: TypeAffinity::Text,
1661 is_ipk: false,
1662 not_null: true,
1663 },
1664 ColumnDef {
1665 name: "email".to_owned(),
1666 affinity: TypeAffinity::Text,
1667 is_ipk: false,
1668 not_null: false,
1669 },
1670 ],
1671 without_rowid: false,
1672 strict: false,
1673 });
1674 schema.add_table(TableDef {
1675 name: "orders".to_owned(),
1676 columns: vec![
1677 ColumnDef {
1678 name: "id".to_owned(),
1679 affinity: TypeAffinity::Integer,
1680 is_ipk: true,
1681 not_null: true,
1682 },
1683 ColumnDef {
1684 name: "user_id".to_owned(),
1685 affinity: TypeAffinity::Integer,
1686 is_ipk: false,
1687 not_null: true,
1688 },
1689 ColumnDef {
1690 name: "amount".to_owned(),
1691 affinity: TypeAffinity::Real,
1692 is_ipk: false,
1693 not_null: false,
1694 },
1695 ],
1696 without_rowid: false,
1697 strict: false,
1698 });
1699 schema
1700 }
1701
1702 fn parse_one(sql: &str) -> Statement {
1703 let mut p = Parser::from_sql(sql);
1704 let (stmts, errs) = p.parse_all();
1705 assert!(errs.is_empty(), "parse errors: {errs:?}");
1706 assert_eq!(stmts.len(), 1);
1707 stmts.into_iter().next().unwrap()
1708 }
1709
1710 #[test]
1713 fn test_schema_find_table_case_insensitive() {
1714 let schema = make_schema();
1715 assert!(schema.find_table("users").is_some());
1716 assert!(schema.find_table("USERS").is_some());
1717 assert!(schema.find_table("Users").is_some());
1718 assert!(schema.find_table("nonexistent").is_none());
1719 }
1720
1721 #[test]
1722 fn test_schema_find_table_in_named_namespace() {
1723 let mut schema = make_schema();
1724 schema.add_table_in_schema(
1725 "aux",
1726 TableDef {
1727 name: "users".to_owned(),
1728 columns: vec![ColumnDef {
1729 name: "nickname".to_owned(),
1730 affinity: TypeAffinity::Text,
1731 is_ipk: false,
1732 not_null: false,
1733 }],
1734 without_rowid: false,
1735 strict: false,
1736 },
1737 );
1738
1739 assert!(schema.find_table_in_schema(Some("main"), "users").is_some());
1740 assert!(schema.find_table_in_schema(Some("aux"), "users").is_some());
1741 assert!(schema.find_table_in_schema(Some("AUX"), "USERS").is_some());
1742 assert!(
1743 schema
1744 .find_table_in_schema(Some("missing"), "users")
1745 .is_none()
1746 );
1747 }
1748
1749 #[test]
1750 fn test_table_find_column() {
1751 let schema = make_schema();
1752 let users = schema.find_table("users").unwrap();
1753 assert!(users.has_column("id"));
1754 assert!(users.has_column("ID"));
1755 assert!(!users.has_column("nonexistent"));
1756 }
1757
1758 #[test]
1759 fn test_table_rowid_alias() {
1760 let schema = make_schema();
1761 let users = schema.find_table("users").unwrap();
1762 assert!(users.is_rowid_alias("rowid"));
1763 assert!(users.is_rowid_alias("_rowid_"));
1764 assert!(users.is_rowid_alias("oid"));
1765 assert!(users.is_rowid_alias("id")); assert!(!users.is_rowid_alias("name"));
1767 }
1768
1769 #[test]
1770 fn test_table_rowid_alias_respects_shadowing() {
1771 let mut schema = Schema::new();
1772 schema.add_table(TableDef {
1773 name: "shadowed".to_owned(),
1774 columns: vec![
1775 ColumnDef {
1776 name: "rowid".to_owned(),
1777 affinity: TypeAffinity::Text,
1778 is_ipk: false,
1779 not_null: false,
1780 },
1781 ColumnDef {
1782 name: "_rowid_".to_owned(),
1783 affinity: TypeAffinity::Text,
1784 is_ipk: false,
1785 not_null: false,
1786 },
1787 ColumnDef {
1788 name: "id".to_owned(),
1789 affinity: TypeAffinity::Integer,
1790 is_ipk: true,
1791 not_null: false,
1792 },
1793 ],
1794 without_rowid: false,
1795 strict: false,
1796 });
1797
1798 let shadowed = schema.find_table("shadowed").unwrap();
1799 assert!(!shadowed.is_rowid_alias("rowid"));
1800 assert!(!shadowed.is_rowid_alias("_rowid_"));
1801 assert!(shadowed.is_rowid_alias("oid"));
1802 assert!(shadowed.is_rowid_alias("id"));
1803 }
1804
1805 #[test]
1806 fn test_table_rowid_alias_disabled_for_without_rowid_tables() {
1807 let mut schema = Schema::new();
1808 schema.add_table(TableDef {
1809 name: "wr".to_owned(),
1810 columns: vec![
1811 ColumnDef {
1812 name: "id".to_owned(),
1813 affinity: TypeAffinity::Integer,
1814 is_ipk: true,
1815 not_null: true,
1816 },
1817 ColumnDef {
1818 name: "payload".to_owned(),
1819 affinity: TypeAffinity::Text,
1820 is_ipk: false,
1821 not_null: false,
1822 },
1823 ],
1824 without_rowid: true,
1825 strict: false,
1826 });
1827
1828 let wr = schema.find_table("wr").unwrap();
1829 assert!(!wr.is_rowid_alias("rowid"));
1830 assert!(!wr.is_rowid_alias("_rowid_"));
1831 assert!(!wr.is_rowid_alias("oid"));
1832 assert!(!wr.is_rowid_alias("id"));
1833 assert!(wr.has_column("id"));
1834 }
1835
1836 #[test]
1839 fn test_scope_resolve_qualified_column() {
1840 let mut scope = Scope::root();
1841 let schema = make_schema();
1842 let cols: HashSet<String> = ["id", "name", "email"]
1843 .iter()
1844 .map(ToString::to_string)
1845 .collect();
1846 scope.add_alias("u", "users", Some(cols));
1847
1848 assert_eq!(
1849 scope.resolve_column(&schema, Some("u"), "id"),
1850 ResolveResult::Resolved("u".to_string())
1851 );
1852 assert_eq!(
1853 scope.resolve_column(&schema, Some("u"), "nonexistent"),
1854 ResolveResult::ColumnNotFound
1855 );
1856 assert_eq!(
1857 scope.resolve_column(&schema, Some("x"), "id"),
1858 ResolveResult::TableNotFound
1859 );
1860 }
1861
1862 #[test]
1863 fn test_scope_resolve_unqualified_column() {
1864 let mut scope = Scope::root();
1865 let schema = make_schema();
1866 scope.add_alias(
1867 "u",
1868 "users",
1869 Some(["id", "name"].iter().map(ToString::to_string).collect()),
1870 );
1871 scope.add_alias(
1872 "o",
1873 "orders",
1874 Some(["id", "user_id"].iter().map(ToString::to_string).collect()),
1875 );
1876
1877 assert_eq!(
1879 scope.resolve_column(&schema, None, "name"),
1880 ResolveResult::Resolved("u".to_string())
1881 );
1882
1883 assert_eq!(
1885 scope.resolve_column(&schema, None, "user_id"),
1886 ResolveResult::Resolved("o".to_string())
1887 );
1888
1889 match scope.resolve_column(&schema, None, "id") {
1891 ResolveResult::Ambiguous(candidates) => {
1892 assert_eq!(candidates.len(), 2);
1893 }
1894 other => panic!("expected Ambiguous, got {other:?}"),
1895 }
1896
1897 assert_eq!(
1899 scope.resolve_column(&schema, None, "nonexistent"),
1900 ResolveResult::ColumnNotFound
1901 );
1902 }
1903
1904 #[test]
1905 fn test_scope_child_inherits_parent() {
1906 let mut parent = Scope::root();
1907 let schema = make_schema();
1908 parent.add_alias(
1909 "u",
1910 "users",
1911 Some(["id", "name"].iter().map(ToString::to_string).collect()),
1912 );
1913 let child = Scope::child(parent);
1914
1915 assert_eq!(
1917 child.resolve_column(&schema, Some("u"), "id"),
1918 ResolveResult::Resolved("u".to_string())
1919 );
1920 }
1921
1922 #[test]
1925 fn test_resolve_simple_select() {
1926 let schema = make_schema();
1927 let stmt = parse_one("SELECT id, name FROM users");
1928 let mut resolver = Resolver::new(&schema);
1929 let errors = resolver.resolve_statement(&stmt);
1930 assert!(errors.is_empty(), "unexpected errors: {errors:?}");
1931 assert_eq!(resolver.tables_resolved, 1);
1932 assert_eq!(resolver.columns_bound, 2);
1933 }
1934
1935 #[test]
1936 fn test_resolve_qualified_column() {
1937 let schema = make_schema();
1938 let stmt = parse_one("SELECT u.id, u.name FROM users u");
1939 let mut resolver = Resolver::new(&schema);
1940 let errors = resolver.resolve_statement(&stmt);
1941 assert!(errors.is_empty(), "unexpected errors: {errors:?}");
1942 assert_eq!(resolver.tables_resolved, 1);
1943 assert_eq!(resolver.columns_bound, 2);
1944 }
1945
1946 #[test]
1947 fn test_resolve_select_from_named_namespace() {
1948 let mut schema = make_schema();
1949 schema.add_table_in_schema(
1950 "aux",
1951 TableDef {
1952 name: "users".to_owned(),
1953 columns: vec![
1954 ColumnDef {
1955 name: "id".to_owned(),
1956 affinity: TypeAffinity::Integer,
1957 is_ipk: true,
1958 not_null: true,
1959 },
1960 ColumnDef {
1961 name: "nickname".to_owned(),
1962 affinity: TypeAffinity::Text,
1963 is_ipk: false,
1964 not_null: false,
1965 },
1966 ],
1967 without_rowid: false,
1968 strict: false,
1969 },
1970 );
1971
1972 let stmt = parse_one("SELECT nickname FROM aux.users");
1973 let mut resolver = Resolver::new(&schema);
1974 let errors = resolver.resolve_statement(&stmt);
1975 assert!(errors.is_empty(), "unexpected errors: {errors:?}");
1976 assert_eq!(resolver.tables_resolved, 1);
1977 assert_eq!(resolver.columns_bound, 1);
1978 }
1979
1980 #[test]
1981 fn test_resolve_named_namespace_does_not_fall_back_to_main_schema() {
1982 let mut schema = make_schema();
1983 schema.add_table_in_schema(
1984 "aux",
1985 TableDef {
1986 name: "users".to_owned(),
1987 columns: vec![
1988 ColumnDef {
1989 name: "id".to_owned(),
1990 affinity: TypeAffinity::Integer,
1991 is_ipk: true,
1992 not_null: true,
1993 },
1994 ColumnDef {
1995 name: "nickname".to_owned(),
1996 affinity: TypeAffinity::Text,
1997 is_ipk: false,
1998 not_null: false,
1999 },
2000 ],
2001 without_rowid: false,
2002 strict: false,
2003 },
2004 );
2005
2006 let stmt = parse_one("SELECT name FROM aux.users");
2007 let mut resolver = Resolver::new(&schema);
2008 let errors = resolver.resolve_statement(&stmt);
2009 assert_eq!(errors.len(), 1, "expected unresolved aux.users.name");
2010 assert!(matches!(
2011 errors[0].kind,
2012 SemanticErrorKind::UnresolvedColumn { .. }
2013 ));
2014 }
2015
2016 #[test]
2017 fn test_resolve_join() {
2018 let schema = make_schema();
2019 let stmt =
2020 parse_one("SELECT u.name, o.amount FROM users u JOIN orders o ON u.id = o.user_id");
2021 let mut resolver = Resolver::new(&schema);
2022 let errors = resolver.resolve_statement(&stmt);
2023 assert!(errors.is_empty(), "unexpected errors: {errors:?}");
2024 assert_eq!(resolver.tables_resolved, 2);
2025 assert_eq!(resolver.columns_bound, 4); }
2027
2028 #[test]
2029 fn test_resolve_join_using() {
2030 let schema = make_schema();
2031 let stmt = parse_one("SELECT u.name, o.amount FROM users u JOIN orders o USING (id)");
2032 let mut resolver = Resolver::new(&schema);
2033 let errors = resolver.resolve_statement(&stmt);
2034 assert!(errors.is_empty(), "unexpected errors: {errors:?}");
2035 assert_eq!(resolver.tables_resolved, 2);
2036 assert_eq!(resolver.columns_bound, 3); }
2038
2039 #[test]
2040 fn test_resolve_unresolved_table() {
2041 let schema = make_schema();
2042 let stmt = parse_one("SELECT * FROM nonexistent");
2043 let mut resolver = Resolver::new(&schema);
2044 let errors = resolver.resolve_statement(&stmt);
2045 assert_eq!(errors.len(), 1);
2046 assert!(matches!(
2047 errors[0].kind,
2048 SemanticErrorKind::UnresolvedTable { .. }
2049 ));
2050 }
2051
2052 #[test]
2053 fn test_resolve_unresolved_column() {
2054 let schema = make_schema();
2055 let stmt = parse_one("SELECT nonexistent FROM users");
2056 let mut resolver = Resolver::new(&schema);
2057 let errors = resolver.resolve_statement(&stmt);
2058 assert_eq!(errors.len(), 1);
2059 assert!(matches!(
2060 errors[0].kind,
2061 SemanticErrorKind::UnresolvedColumn { .. }
2062 ));
2063 }
2064
2065 #[test]
2066 fn test_unaliased_subqueries() {
2067 let schema = make_schema();
2068 let stmt = parse_one("SELECT a FROM (SELECT 1), (SELECT 2)");
2070 let mut resolver = Resolver::new(&schema);
2071 let errors = resolver.resolve_statement(&stmt);
2072 assert_eq!(errors.len(), 1, "Expected unresolved column error!");
2073 assert!(matches!(
2074 errors[0].kind,
2075 SemanticErrorKind::UnresolvedColumn { .. }
2076 ));
2077 }
2078
2079 #[test]
2080 fn test_resolve_ambiguous_column() {
2081 let schema = make_schema();
2082 let stmt = parse_one("SELECT id FROM users, orders");
2083 let mut resolver = Resolver::new(&schema);
2084 let errors = resolver.resolve_statement(&stmt);
2085 assert_eq!(errors.len(), 1);
2086 assert!(matches!(
2087 errors[0].kind,
2088 SemanticErrorKind::AmbiguousColumn { .. }
2089 ));
2090 }
2091
2092 #[test]
2093 fn test_resolve_where_clause() {
2094 let schema = make_schema();
2095 let stmt = parse_one("SELECT name FROM users WHERE id > 10");
2096 let mut resolver = Resolver::new(&schema);
2097 let errors = resolver.resolve_statement(&stmt);
2098 assert!(errors.is_empty(), "unexpected errors: {errors:?}");
2099 assert_eq!(resolver.columns_bound, 2); }
2101
2102 #[test]
2103 fn test_resolve_star_select() {
2104 let schema = make_schema();
2105 let stmt = parse_one("SELECT * FROM users");
2106 let mut resolver = Resolver::new(&schema);
2107 let errors = resolver.resolve_statement(&stmt);
2108 assert!(errors.is_empty(), "unexpected errors: {errors:?}");
2109 assert_eq!(resolver.tables_resolved, 1);
2110 }
2111
2112 #[test]
2113 fn test_resolve_schema_qualified_table_star() {
2114 let mut schema = make_schema();
2115 schema.add_table_in_schema(
2116 "aux",
2117 TableDef {
2118 name: "users".to_owned(),
2119 columns: vec![
2120 ColumnDef {
2121 name: "id".to_owned(),
2122 affinity: TypeAffinity::Integer,
2123 is_ipk: true,
2124 not_null: true,
2125 },
2126 ColumnDef {
2127 name: "nickname".to_owned(),
2128 affinity: TypeAffinity::Text,
2129 is_ipk: false,
2130 not_null: false,
2131 },
2132 ],
2133 without_rowid: false,
2134 strict: false,
2135 },
2136 );
2137
2138 let stmt = parse_one("SELECT aux.users.* FROM aux.users");
2139 let mut resolver = Resolver::new(&schema);
2140 let errors = resolver.resolve_statement(&stmt);
2141 assert!(errors.is_empty(), "unexpected errors: {errors:?}");
2142 assert_eq!(resolver.tables_resolved, 1);
2143 }
2144
2145 #[test]
2146 fn test_resolve_star_in_subquery_without_tables() {
2147 let schema = make_schema();
2148 let stmt = parse_one("SELECT (SELECT *) FROM users");
2149 let mut resolver = Resolver::new(&schema);
2150 let errors = resolver.resolve_statement(&stmt);
2151 assert_eq!(errors.len(), 1);
2152 assert!(matches!(
2153 errors[0].kind,
2154 SemanticErrorKind::NoTablesSpecifiedForStar
2155 ));
2156 }
2157
2158 #[test]
2159 fn test_resolve_insert_checks_table() {
2160 let schema = make_schema();
2161 let stmt = parse_one("INSERT INTO nonexistent VALUES (1)");
2162 let mut resolver = Resolver::new(&schema);
2163 let errors = resolver.resolve_statement(&stmt);
2164 assert_eq!(errors.len(), 1);
2165 assert!(matches!(
2166 errors[0].kind,
2167 SemanticErrorKind::UnresolvedTable { .. }
2168 ));
2169 }
2170
2171 #[test]
2172 fn test_resolve_rowid_column() {
2173 let schema = make_schema();
2174 let stmt = parse_one("SELECT rowid, _rowid_, oid FROM users");
2175 let mut resolver = Resolver::new(&schema);
2176 let errors = resolver.resolve_statement(&stmt);
2177 assert!(errors.is_empty(), "unexpected errors: {errors:?}");
2178 }
2179
2180 #[test]
2181 fn test_order_by_select_alias_shadowing() {
2182 let mut schema = Schema::new();
2183 schema.add_table(TableDef {
2184 name: "tbl".to_owned(),
2185 columns: vec![ColumnDef {
2186 name: "a".to_owned(),
2187 affinity: TypeAffinity::Integer,
2188 is_ipk: false,
2189 not_null: false,
2190 }],
2191 without_rowid: false,
2192 strict: false,
2193 });
2194
2195 let stmt = parse_one("SELECT 1 AS a FROM tbl ORDER BY a");
2197 let mut resolver = Resolver::new(&schema);
2198 let errors = resolver.resolve_statement(&stmt);
2199
2200 if !errors.is_empty() {
2203 panic!("Expected no errors, but got: {:?}", errors);
2204 }
2205 }
2206
2207 #[test]
2208 fn test_compound_order_by_can_resolve_alias_from_later_arm() {
2209 let schema = make_schema();
2210 let stmt = parse_one("SELECT 1 AS a UNION SELECT 2 AS b ORDER BY b");
2211 let mut resolver = Resolver::new(&schema);
2212 let errors = resolver.resolve_statement(&stmt);
2213 assert!(errors.is_empty(), "unexpected errors: {errors:?}");
2214 }
2215
2216 #[test]
2217 fn test_compound_order_by_can_match_output_expression_from_later_arm() {
2218 let mut schema = Schema::new();
2219 schema.add_table(TableDef {
2220 name: "tbl".to_owned(),
2221 columns: vec![
2222 ColumnDef {
2223 name: "a".to_owned(),
2224 affinity: TypeAffinity::Integer,
2225 is_ipk: false,
2226 not_null: false,
2227 },
2228 ColumnDef {
2229 name: "b".to_owned(),
2230 affinity: TypeAffinity::Integer,
2231 is_ipk: false,
2232 not_null: false,
2233 },
2234 ],
2235 without_rowid: false,
2236 strict: false,
2237 });
2238
2239 let stmt = parse_one("SELECT a + 1 FROM tbl UNION SELECT b + 1 FROM tbl ORDER BY b + 1");
2240 let mut resolver = Resolver::new(&schema);
2241 let errors = resolver.resolve_statement(&stmt);
2242 assert!(errors.is_empty(), "unexpected errors: {errors:?}");
2243 }
2244
2245 #[test]
2248 fn test_semantic_metrics() {
2249 let before = semantic_metrics_snapshot();
2252 let schema = make_schema();
2253
2254 let stmt = parse_one("SELECT nonexistent FROM users");
2256 let mut resolver = Resolver::new(&schema);
2257 let _ = resolver.resolve_statement(&stmt);
2258
2259 let after = semantic_metrics_snapshot();
2260 assert!(
2261 after.fsqlite_semantic_errors_total > before.fsqlite_semantic_errors_total,
2262 "expected at least 1 new semantic error, before={}, after={}",
2263 before.fsqlite_semantic_errors_total,
2264 after.fsqlite_semantic_errors_total,
2265 );
2266 }
2267
2268 #[test]
2269 fn test_resolve_function_arity() {
2270 let schema = make_schema();
2271 let stmt = parse_one("SELECT sum(1, 2)");
2272 let mut resolver = Resolver::new(&schema);
2273 let errors = resolver.resolve_statement(&stmt);
2274 assert_eq!(errors.len(), 1);
2275 assert!(matches!(
2276 errors[0].kind,
2277 SemanticErrorKind::FunctionArityMismatch { .. }
2278 ));
2279 }
2280
2281 #[test]
2282 fn test_resolve_group_by_alias() {
2283 let schema = make_schema();
2284 let stmt = parse_one("SELECT id AS x FROM users GROUP BY x");
2285 let mut resolver = Resolver::new(&schema);
2286 let errors = resolver.resolve_statement(&stmt);
2287 assert!(errors.is_empty(), "unexpected errors: {errors:?}");
2288 }
2289
2290 #[test]
2291 fn test_resolve_escape_on_non_like() {
2292 let schema = make_schema();
2293 let stmt_like = parse_one("SELECT 1 LIKE 2 ESCAPE 3");
2295 let mut resolver_like = Resolver::new(&schema);
2296 let errors_like = resolver_like.resolve_statement(&stmt_like);
2297 assert!(errors_like.is_empty(), "LIKE ESCAPE should be valid");
2298
2299 let stmt_glob = parse_one("SELECT 1 GLOB 2 ESCAPE 3");
2301 let mut resolver_glob = Resolver::new(&schema);
2302 let errors_glob = resolver_glob.resolve_statement(&stmt_glob);
2303 assert_eq!(errors_glob.len(), 1);
2304 assert!(matches!(
2305 errors_glob[0].kind,
2306 SemanticErrorKind::FunctionArityMismatch { .. }
2307 ));
2308 }
2309
2310 #[test]
2311 fn test_update_assignment_target_strict() {
2312 let schema = make_schema();
2313 let stmt = parse_one("WITH cte(amount) AS (SELECT 1) UPDATE users SET amount = 1 FROM cte");
2319 let mut resolver = Resolver::new(&schema);
2320 let errors = resolver.resolve_statement(&stmt);
2321 assert_eq!(
2322 errors.len(),
2323 1,
2324 "Should report amount as unresolved for users table, instead got: {:?}",
2325 errors
2326 );
2327 }
2328
2329 #[test]
2330 fn test_rowid_resolution() {
2331 let schema = make_schema();
2332 let mut p = Parser::from_sql("SELECT rowid FROM users");
2333 let (stmts, _) = p.parse_all();
2334 let stmt = stmts.into_iter().next().unwrap();
2335 let mut resolver = Resolver::new(&schema);
2336 let errors = resolver.resolve_statement(&stmt);
2337 assert!(errors.is_empty(), "errors: {:?}", errors);
2338 }
2339}