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