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