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