nodedb_sql/resolver/expr/convert/
identifier.rs1use sqlparser::ast::Ident;
6
7use crate::error::{Result, SqlError};
8use crate::parser::normalize::{SCHEMA_QUALIFIED_MSG, normalize_ident};
9use crate::resolver::ColumnScope;
10use crate::types::*;
11
12fn is_zero_arg_keyword_function(name: &str) -> bool {
16 matches!(
17 name,
18 "current_timestamp"
19 | "current_date"
20 | "current_time"
21 | "localtime"
22 | "localtimestamp"
23 | "current_user"
24 | "current_role"
25 | "current_schema"
26 | "session_user"
27 | "user"
28 | "version"
29 )
30}
31
32pub(super) fn convert_identifier(ident: &Ident, scope: &ColumnScope<'_>) -> Result<SqlExpr> {
33 let name = normalize_ident(ident);
34 if is_zero_arg_keyword_function(&name) {
39 return Ok(SqlExpr::Function {
40 name,
41 args: vec![],
42 distinct: false,
43 });
44 }
45 let table = scope.column_qualifier(None, &name)?;
46 Ok(SqlExpr::Column { table, name })
47}
48
49pub(super) fn convert_compound_identifier(
50 parts: &[Ident],
51 scope: &ColumnScope<'_>,
52) -> Result<SqlExpr> {
53 if parts.len() >= 3 {
54 let qualified: String = parts
55 .iter()
56 .map(normalize_ident)
57 .collect::<Vec<_>>()
58 .join(".");
59 return Err(SqlError::Unsupported {
60 detail: format!(
61 "schema-qualified column reference '{qualified}': {SCHEMA_QUALIFIED_MSG}"
62 ),
63 });
64 }
65 let table = normalize_ident(&parts[0]);
66 let name = normalize_ident(&parts[1]);
67 let table = scope.column_qualifier(Some(&table), &name)?;
68 Ok(SqlExpr::Column { table, name })
69}
70
71#[cfg(test)]
72mod tests {
73 use sqlparser::ast::Expr;
74
75 use crate::error::SqlError;
76 use crate::resolver::ColumnScope;
77 use crate::resolver::expr::convert::convert_expr;
78 use crate::resolver::expr::convert::entry::tests::first_select_expr;
79 use crate::types::*;
80
81 #[test]
82 fn compound_identifier_two_parts_is_column() {
83 let expr = first_select_expr("SELECT t.col FROM t");
84 let result = convert_expr(&expr, &ColumnScope::Unchecked).expect("should succeed");
85 match result {
86 SqlExpr::Column {
87 table: Some(t),
88 name,
89 } => {
90 assert_eq!(t, "t");
91 assert_eq!(name, "col");
92 }
93 other => panic!("expected Column with table, got {other:?}"),
94 }
95 }
96
97 #[test]
98 fn compound_identifier_three_parts_rejected() {
99 use sqlparser::ast::Ident;
101 let parts = vec![Ident::new("schema"), Ident::new("table"), Ident::new("col")];
102 let expr = Expr::CompoundIdentifier(parts);
103 let err = convert_expr(&expr, &ColumnScope::Unchecked).unwrap_err();
104 assert!(
105 matches!(err, SqlError::Unsupported { .. }),
106 "expected Unsupported, got {err:?}"
107 );
108 let msg = format!("{err}");
109 assert!(
110 msg.contains("schema.table.col") || msg.contains("schema-qualified"),
111 "error should mention the qualified name: {msg}"
112 );
113 }
114
115 #[test]
116 fn compound_identifier_four_parts_rejected() {
117 use sqlparser::ast::Ident;
118 let parts = vec![
119 Ident::new("a"),
120 Ident::new("b"),
121 Ident::new("c"),
122 Ident::new("d"),
123 ];
124 let expr = Expr::CompoundIdentifier(parts);
125 let err = convert_expr(&expr, &ColumnScope::Unchecked).unwrap_err();
126 assert!(
127 matches!(err, SqlError::Unsupported { .. }),
128 "expected Unsupported, got {err:?}"
129 );
130 }
131
132 #[test]
135 fn double_quoted_is_identifier_not_literal() {
136 let expr = first_select_expr(r#"SELECT "userId" FROM users"#);
137 match expr {
138 Expr::Identifier(ident) => {
139 assert_eq!(ident.value, "userId");
140 assert_eq!(ident.quote_style, Some('"'));
141 }
142 other => panic!("expected Identifier, got {other:?}"),
143 }
144 }
145
146 #[test]
149 fn double_quoted_select_col_case_preserved() {
150 let expr = first_select_expr(r#"SELECT "userId" FROM users"#);
151 let sql_expr =
152 convert_expr(&expr, &ColumnScope::Unchecked).expect("convert_expr should succeed");
153 match sql_expr {
154 SqlExpr::Column { name, table } => {
155 assert_eq!(
156 name, "userId",
157 "case must be preserved for quoted identifier"
158 );
159 assert_eq!(table, None, "no table qualifier expected");
160 }
161 other => panic!("expected Column, got {other:?}"),
162 }
163 }
164}