koron_query_parser/
table.rs

1use std::fmt::{self, Display};
2
3use crate::error::ParseError;
4use serde::{Deserialize, Serialize};
5use sqlparser::ast;
6use utoipa::{IntoParams, ToSchema};
7
8use super::{internal, unsupported};
9
10use super::support::case_fold_identifier;
11
12pub(crate) struct TableIdentWithAlias(pub TabIdent, pub Option<String>);
13
14impl TableIdentWithAlias {
15    pub(crate) fn extract(from: &[ast::TableWithJoins]) -> Result<Self, ParseError> {
16        let multi_tables = || {
17            Err(unsupported!("the FROM clause has multiple tables \
18                         (no JOINs, subqueries or functions allowed)."
19                .to_string()))
20        };
21
22        let relation = match from {
23            [ast::TableWithJoins { relation, joins }] if joins.is_empty() => relation,
24            _ => return multi_tables(),
25        };
26
27        match relation {
28            ast::TableFactor::Table {
29                name,
30                alias,
31                args,
32                with_hints,
33                version,
34                partitions,
35            } => {
36                if args.is_some() {
37                    return multi_tables();
38                }
39                if !with_hints.is_empty() {
40                    return Err(unsupported!(
41                        "table hints (WITH in FROM clauses).".to_string()
42                    ));
43                }
44                if version.is_some() {
45                    return Err(unsupported!("version qualifier.".to_string()));
46                }
47                if !partitions.is_empty() {
48                    return Err(unsupported!("table partitions.".to_string()));
49                }
50                let table = TabIdent::from_object_name(name)?;
51                let alias = alias
52                    .as_ref()
53                    .map(|alias| {
54                        let ast::TableAlias { name, columns } = alias;
55                        if columns.is_empty() {
56                            Ok(case_fold_identifier(name))
57                        } else {
58                            Err(unsupported!(format!(
59                                "table aliases with columns (such as {alias})."
60                            )))
61                        }
62                    })
63                    .transpose()?;
64                Ok(Self(table, alias))
65            }
66            ast::TableFactor::Derived { .. }
67            | ast::TableFactor::TableFunction { .. }
68            | ast::TableFactor::UNNEST { .. }
69            | ast::TableFactor::NestedJoin { .. }
70            | ast::TableFactor::Pivot { .. }
71            | ast::TableFactor::Function { .. }
72            | ast::TableFactor::JsonTable { .. }
73            | ast::TableFactor::Unpivot { .. } => multi_tables(),
74        }
75    }
76}
77
78#[derive(PartialEq, Eq, Clone, Debug, Serialize, Deserialize, Default, ToSchema, IntoParams)]
79pub struct TabIdent {
80    pub db: Option<String>,
81    pub schema: Option<String>,
82    pub table: String,
83}
84
85impl TabIdent {
86    fn from_object_name(object_name: &ast::ObjectName) -> Result<Self, ParseError> {
87        let ast::ObjectName(name_parts) = object_name;
88        match &name_parts[..] {
89            [] => Err(internal!(
90                "found empty table name (ObjectName) in query AST.".to_string()
91            )),
92            [table] => Ok(Self {
93                db: None,
94                schema: None,
95                table: case_fold_identifier(table),
96            }),
97            [schema, table] => Ok(Self {
98                db: None,
99                schema: Some(case_fold_identifier(schema)),
100                table: case_fold_identifier(table),
101            }),
102            [db, schema, table] => Ok(Self {
103                db: Some(case_fold_identifier(db)),
104                schema: Some(case_fold_identifier(schema)),
105                table: case_fold_identifier(table),
106            }),
107            [..] => Err(internal!(format!(
108                "found too many ident in table name (i.e., {object_name}) in query AST."
109            ))),
110        }
111    }
112
113    #[must_use]
114    pub fn into_object_name(&self, quote_style: Option<char>) -> ast::ObjectName {
115        let mut objects = vec![];
116        if let Some(db) = self.db.clone() {
117            objects.push(ast::Ident {
118                value: db,
119                quote_style,
120            });
121        }
122        if let Some(schema) = self.schema.clone() {
123            objects.push(ast::Ident {
124                value: schema,
125                quote_style,
126            });
127        }
128        objects.push(ast::Ident {
129            value: self.table.clone(),
130            quote_style,
131        });
132        ast::ObjectName(objects)
133    }
134}
135
136impl Display for TabIdent {
137    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
138        match (&self.db, &self.schema, &self.table) {
139            (Some(db), Some(schema), table_name) => {
140                write!(f, "{db}.{schema}.{table_name}")
141            }
142            (None, Some(schema), table_name) => {
143                write!(f, "{schema}.{table_name}")
144            }
145            (Some(db), None, table_name) => {
146                write!(f, "{db}.{table_name}")
147            }
148            (None, None, table_name) => {
149                write!(f, "{table_name}")
150            }
151        }
152    }
153}