koron_query_parser/
table.rs1use 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}