use sqlparser::ast::Ident;
use crate::error::{Result, SqlError};
use crate::parser::normalize::{SCHEMA_QUALIFIED_MSG, normalize_ident};
use crate::resolver::ColumnScope;
use crate::types::*;
fn is_zero_arg_keyword_function(name: &str) -> bool {
matches!(
name,
"current_timestamp"
| "current_date"
| "current_time"
| "localtime"
| "localtimestamp"
| "current_user"
| "current_role"
| "current_schema"
| "session_user"
| "user"
| "version"
)
}
pub(super) fn convert_identifier(ident: &Ident, scope: &ColumnScope<'_>) -> Result<SqlExpr> {
let name = normalize_ident(ident);
if is_zero_arg_keyword_function(&name) {
return Ok(SqlExpr::Function {
name,
args: vec![],
distinct: false,
});
}
let table = scope.column_qualifier(None, &name)?;
Ok(SqlExpr::Column { table, name })
}
pub(super) fn convert_compound_identifier(
parts: &[Ident],
scope: &ColumnScope<'_>,
) -> Result<SqlExpr> {
if parts.len() >= 3 {
let qualified: String = parts
.iter()
.map(normalize_ident)
.collect::<Vec<_>>()
.join(".");
return Err(SqlError::Unsupported {
detail: format!(
"schema-qualified column reference '{qualified}': {SCHEMA_QUALIFIED_MSG}"
),
});
}
let table = normalize_ident(&parts[0]);
let name = normalize_ident(&parts[1]);
let table = scope.column_qualifier(Some(&table), &name)?;
Ok(SqlExpr::Column { table, name })
}
#[cfg(test)]
mod tests {
use sqlparser::ast::Expr;
use crate::error::SqlError;
use crate::resolver::ColumnScope;
use crate::resolver::expr::convert::convert_expr;
use crate::resolver::expr::convert::entry::tests::first_select_expr;
use crate::types::*;
#[test]
fn compound_identifier_two_parts_is_column() {
let expr = first_select_expr("SELECT t.col FROM t");
let result = convert_expr(&expr, &ColumnScope::Unchecked).expect("should succeed");
match result {
SqlExpr::Column {
table: Some(t),
name,
} => {
assert_eq!(t, "t");
assert_eq!(name, "col");
}
other => panic!("expected Column with table, got {other:?}"),
}
}
#[test]
fn compound_identifier_three_parts_rejected() {
use sqlparser::ast::Ident;
let parts = vec![Ident::new("schema"), Ident::new("table"), Ident::new("col")];
let expr = Expr::CompoundIdentifier(parts);
let err = convert_expr(&expr, &ColumnScope::Unchecked).unwrap_err();
assert!(
matches!(err, SqlError::Unsupported { .. }),
"expected Unsupported, got {err:?}"
);
let msg = format!("{err}");
assert!(
msg.contains("schema.table.col") || msg.contains("schema-qualified"),
"error should mention the qualified name: {msg}"
);
}
#[test]
fn compound_identifier_four_parts_rejected() {
use sqlparser::ast::Ident;
let parts = vec![
Ident::new("a"),
Ident::new("b"),
Ident::new("c"),
Ident::new("d"),
];
let expr = Expr::CompoundIdentifier(parts);
let err = convert_expr(&expr, &ColumnScope::Unchecked).unwrap_err();
assert!(
matches!(err, SqlError::Unsupported { .. }),
"expected Unsupported, got {err:?}"
);
}
#[test]
fn double_quoted_is_identifier_not_literal() {
let expr = first_select_expr(r#"SELECT "userId" FROM users"#);
match expr {
Expr::Identifier(ident) => {
assert_eq!(ident.value, "userId");
assert_eq!(ident.quote_style, Some('"'));
}
other => panic!("expected Identifier, got {other:?}"),
}
}
#[test]
fn double_quoted_select_col_case_preserved() {
let expr = first_select_expr(r#"SELECT "userId" FROM users"#);
let sql_expr =
convert_expr(&expr, &ColumnScope::Unchecked).expect("convert_expr should succeed");
match sql_expr {
SqlExpr::Column { name, table } => {
assert_eq!(
name, "userId",
"case must be preserved for quoted identifier"
);
assert_eq!(table, None, "no table qualifier expected");
}
other => panic!("expected Column, got {other:?}"),
}
}
}