use core::fmt;
use crate::casing::IdentifierCasing;
use crate::error::Error;
use sqlparser::ast::{
Expr, GroupByExpr, Ident, Insert, JoinOperator, ObjectName, Query, Select, SelectFlavor,
SelectItem, SetExpr, TableFactor, TableObject,
};
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct TableReference {
#[cfg_attr(
feature = "serde",
serde(serialize_with = "crate::serde_support::opt_ident")
)]
pub catalog: Option<Ident>,
#[cfg_attr(
feature = "serde",
serde(serialize_with = "crate::serde_support::opt_ident")
)]
pub schema: Option<Ident>,
#[cfg_attr(
feature = "serde",
serde(serialize_with = "crate::serde_support::ident")
)]
pub name: Ident,
}
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct TableRead {
pub reference: TableReference,
pub resolution: ResolutionKind,
}
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct TableWrite {
pub reference: TableReference,
pub resolution: ResolutionKind,
}
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct ColumnReference {
pub table: Option<TableReference>,
#[cfg_attr(
feature = "serde",
serde(serialize_with = "crate::serde_support::ident")
)]
pub name: Ident,
}
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct ColumnRead {
pub reference: ColumnReference,
pub resolution: ResolutionKind,
}
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct ColumnWrite {
pub reference: ColumnReference,
pub resolution: ResolutionKind,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub enum ResolutionKind {
Cataloged,
Inferred,
Ambiguous,
Unresolved,
}
impl TableReference {
pub(crate) fn try_from_name(name: &ObjectName) -> Result<Self, Error> {
let parts = name
.0
.iter()
.map(|part| part.as_ident())
.collect::<Option<Vec<&Ident>>>()
.ok_or_else(|| {
Error::AnalysisError(format!(
"table name `{name}` is not a plain identifier path"
))
})?;
match parts.as_slice() {
[] => Err(Error::AnalysisError(
"ObjectName has no identifiers".to_string(),
)),
[n] => Ok(TableReference {
catalog: None,
schema: None,
name: (*n).clone(),
}),
[schema, n] => Ok(TableReference {
catalog: None,
schema: Some((*schema).clone()),
name: (*n).clone(),
}),
[catalog, schema, n] => Ok(TableReference {
catalog: Some((*catalog).clone()),
schema: Some((*schema).clone()),
name: (*n).clone(),
}),
_ => Err(Error::AnalysisError(
"Too many identifiers provided".to_string(),
)),
}
}
pub(crate) fn format_list(tables: &[Self]) -> String {
tables
.iter()
.map(|t| t.to_string())
.collect::<Vec<_>>()
.join(", ")
}
pub(crate) fn try_from_parts(parts: &[Ident]) -> Option<Self> {
match parts {
[name] => Some(TableReference {
catalog: None,
schema: None,
name: name.clone(),
}),
[schema, name] => Some(TableReference {
catalog: None,
schema: Some(schema.clone()),
name: name.clone(),
}),
[catalog, schema, name] => Some(TableReference {
catalog: Some(catalog.clone()),
schema: Some(schema.clone()),
name: name.clone(),
}),
_ => None,
}
}
pub(crate) fn from_insert_with_alias(value: &Insert) -> Result<(Self, Option<Ident>), Error> {
let name = match &value.table {
TableObject::TableName(object_name) => object_name,
TableObject::TableFunction(function) => &function.name,
TableObject::TableQuery(query) => {
match insert_target_view(query)
.as_ref()
.and_then(insert_target_base)
{
Some(name) => name,
None => {
return Err(Error::AnalysisError(
"INSERT target is a subquery over no single base table".to_string(),
))
}
}
}
};
let alias = value.table_alias.as_ref().map(|a| a.alias.clone());
Ok((Self::try_from_name(name)?, alias))
}
pub(crate) fn from_table_factor_with_alias(
table: &TableFactor,
) -> Result<(Self, Option<Ident>), Error> {
match table {
TableFactor::Table { name, alias, .. } => Ok((
Self::try_from_name(name)?,
alias.as_ref().map(|a| a.name.clone()),
)),
_ => Err(Error::AnalysisError(
"TableFactor variant other than Table cannot be converted to a TableReference"
.to_string(),
)),
}
}
}
impl fmt::Display for TableReference {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut parts = Vec::new();
if let Some(catalog) = &self.catalog {
parts.push(catalog.to_string());
}
if let Some(schema) = &self.schema {
parts.push(schema.to_string());
}
parts.push(self.name.to_string());
write!(f, "{}", parts.join("."))
}
}
impl fmt::Display for ColumnReference {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match &self.table {
Some(table) => write!(f, "{table}.{}", self.name),
None => write!(f, "{}", self.name),
}
}
}
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct TableIdentityKey {
catalog: Option<String>,
schema: Option<String>,
name: String,
}
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct ColumnIdentityKey {
table: Option<TableIdentityKey>,
name: String,
}
impl TableReference {
pub fn identity_key(&self, casing: &IdentifierCasing) -> TableIdentityKey {
let fold = |ident: &Ident| casing.table.normalize(ident);
TableIdentityKey {
catalog: self.catalog.as_ref().map(&fold),
schema: self.schema.as_ref().map(&fold),
name: fold(&self.name),
}
}
pub fn same_table(&self, other: &Self, casing: &IdentifierCasing) -> bool {
self.identity_key(casing) == other.identity_key(casing)
}
}
impl ColumnReference {
pub fn identity_key(&self, casing: &IdentifierCasing) -> ColumnIdentityKey {
ColumnIdentityKey {
table: self.table.as_ref().map(|t| t.identity_key(casing)),
name: casing.column.normalize(&self.name),
}
}
pub fn same_column(&self, other: &Self, casing: &IdentifierCasing) -> bool {
self.identity_key(casing) == other.identity_key(casing)
}
}
impl TryFrom<&Insert> for TableReference {
type Error = Error;
fn try_from(value: &Insert) -> Result<Self, Self::Error> {
Self::from_insert_with_alias(value).map(|(table, _)| table)
}
}
impl TryFrom<&TableFactor> for TableReference {
type Error = Error;
fn try_from(table: &TableFactor) -> Result<Self, Self::Error> {
Self::from_table_factor_with_alias(table).map(|(table, _)| table)
}
}
impl TryFrom<&ObjectName> for TableReference {
type Error = Error;
fn try_from(obj_name: &ObjectName) -> Result<Self, Self::Error> {
Self::try_from_name(obj_name)
}
}
pub(crate) struct InsertTargetView<'a> {
pub(crate) factors: Vec<(&'a ObjectName, Option<&'a Ident>)>,
pub(crate) projection: &'a [SelectItem],
pub(crate) join_operators: Vec<&'a JoinOperator>,
pub(crate) selection: Option<&'a Expr>,
}
pub(crate) fn insert_target_view(query: &Query) -> Option<InsertTargetView<'_>> {
let Query {
with: None,
body,
order_by: None,
limit_clause: None,
fetch: None,
locks,
for_clause: None,
settings: None,
format_clause: None,
pipe_operators,
} = query
else {
return None;
};
if !locks.is_empty() || !pipe_operators.is_empty() {
return None;
}
let SetExpr::Select(select) = body.as_ref() else {
return None;
};
let Select {
select_token: _,
optimizer_hints: _,
distinct: None,
select_modifiers: None,
top: None,
top_before_distinct: _,
projection,
exclude: None,
into: None,
from,
lateral_views,
prewhere: None,
selection,
connect_by,
group_by: GroupByExpr::Expressions(group_by, group_by_modifiers),
cluster_by,
distribute_by,
sort_by,
having: None,
named_window,
qualify: None,
window_before_qualify: _,
value_table_mode: None,
flavor: SelectFlavor::Standard,
} = select.as_ref()
else {
return None;
};
if !group_by.is_empty()
|| !group_by_modifiers.is_empty()
|| !cluster_by.is_empty()
|| !distribute_by.is_empty()
|| !sort_by.is_empty()
|| !lateral_views.is_empty()
|| !connect_by.is_empty()
|| !named_window.is_empty()
{
return None;
}
if from.is_empty() {
return None;
}
fn plain_table(factor: &TableFactor) -> Option<(&ObjectName, Option<&Ident>)> {
match factor {
TableFactor::Table {
name,
alias,
args: None,
..
} => Some((name, alias.as_ref().map(|a| &a.name))),
_ => None,
}
}
let mut factors = Vec::new();
let mut join_operators = Vec::new();
for twj in from {
factors.push(plain_table(&twj.relation)?);
for join in &twj.joins {
if matches!(
join.join_operator,
JoinOperator::ArrayJoin
| JoinOperator::LeftArrayJoin
| JoinOperator::InnerArrayJoin
) {
return None;
}
factors.push(plain_table(&join.relation)?);
join_operators.push(&join.join_operator);
}
}
Some(InsertTargetView {
factors,
projection,
join_operators,
selection: selection.as_ref(),
})
}
pub(crate) fn insert_target_base<'a>(view: &InsertTargetView<'a>) -> Option<&'a ObjectName> {
match view.factors.as_slice() {
[(name, _)] => Some(name),
_ => None,
}
}
#[cfg(test)]
mod tests {
use super::*;
use sqlparser::ast::{SetExpr, Statement};
use sqlparser::dialect::GenericDialect;
use sqlparser::parser::Parser;
fn first_table_factor(from: &str) -> TableFactor {
let sql = format!("SELECT 1 FROM {from}");
let mut stmts = Parser::parse_sql(&GenericDialect {}, &sql).unwrap();
let Statement::Query(query) = stmts.remove(0) else {
panic!("expected a query");
};
let SetExpr::Select(select) = *query.body else {
panic!("expected a SELECT");
};
select.from.into_iter().next().unwrap().relation
}
#[test]
fn try_from_object_name_keeps_catalog_schema_name_and_displays_all_parts() {
let factor = first_table_factor("cat.sch.tbl");
let TableFactor::Table { name, .. } = &factor else {
panic!("expected a table factor");
};
let reference = TableReference::try_from(name).unwrap();
assert_eq!(reference.catalog.as_ref().unwrap().value, "cat");
assert_eq!(reference.schema.as_ref().unwrap().value, "sch");
assert_eq!(reference.name.value, "tbl");
assert_eq!(reference.to_string(), "cat.sch.tbl");
}
#[test]
fn try_from_table_factor_converts_a_table_and_rejects_a_derived_factor() {
let table = first_table_factor("a.b");
let reference = TableReference::try_from(&table).unwrap();
assert_eq!(reference.schema.as_ref().unwrap().value, "a");
assert_eq!(reference.name.value, "b");
let derived = first_table_factor("(SELECT 1) AS d");
assert!(matches!(
TableReference::try_from(&derived),
Err(Error::AnalysisError(_))
));
}
#[test]
fn try_from_insert_takes_the_target_name() {
let mut stmts =
Parser::parse_sql(&GenericDialect {}, "INSERT INTO a.b VALUES (1)").unwrap();
let Statement::Insert(insert) = stmts.remove(0) else {
panic!("expected an insert");
};
let reference = TableReference::try_from(&insert).unwrap();
assert_eq!(reference.schema.as_ref().unwrap().value, "a");
assert_eq!(reference.name.value, "b");
}
#[test]
fn from_insert_with_alias_extracts_the_target_alias_identifier() {
use sqlparser::dialect::PostgreSqlDialect;
let mut stmts =
Parser::parse_sql(&PostgreSqlDialect {}, "INSERT INTO t AS foo (a) VALUES (1)")
.unwrap();
let Statement::Insert(insert) = stmts.remove(0) else {
panic!("expected an insert");
};
let (reference, alias) = TableReference::from_insert_with_alias(&insert).unwrap();
assert_eq!(reference.name.value, "t");
assert_eq!(alias.unwrap().value, "foo");
}
#[test]
fn try_from_insert_sees_through_an_inline_view_target() {
use sqlparser::dialect::OracleDialect;
let parse = |sql: &str| {
let mut stmts = Parser::parse_sql(&OracleDialect {}, sql).unwrap();
let Statement::Insert(insert) = stmts.remove(0) else {
panic!("expected an insert");
};
insert
};
let insert = parse("INSERT INTO (SELECT a FROM s.t WHERE a > 0) VALUES (1)");
let reference = TableReference::try_from(&insert).unwrap();
assert_eq!(reference.schema.as_ref().unwrap().value, "s");
assert_eq!(reference.name.value, "t");
let join = parse("INSERT INTO (SELECT a.id FROM a JOIN b ON a.id = b.id) VALUES (1)");
assert!(matches!(
TableReference::try_from(&join),
Err(Error::AnalysisError(_))
));
}
#[test]
fn insert_target_view_rejects_an_array_join_operand() {
use sqlparser::dialect::ClickHouseDialect;
let parse = |sql: &str| {
let mut stmts = Parser::parse_sql(&ClickHouseDialect {}, sql).unwrap();
let Statement::Query(query) = stmts.remove(0) else {
panic!("expected a query");
};
query
};
let plain = parse("SELECT e.id FROM emp e JOIN dept d ON e.id = d.id");
let view = insert_target_view(&plain).unwrap();
assert_eq!(view.factors.len(), 2);
assert_eq!(view.factors[1].1.unwrap().value, "d");
assert_eq!(view.join_operators.len(), 1);
assert_eq!(view.projection.len(), 1);
assert!(view.selection.is_none());
let array_join = parse("SELECT e.id FROM emp e ARRAY JOIN arr");
assert!(insert_target_view(&array_join).is_none());
}
}