Skip to content

Commit a0f511c

Browse files
Encapsulate CreateTable, CreateIndex into specific structs (apache#1291)
1 parent f3f5de5 commit a0f511c

10 files changed

+387
-400
lines changed

src/ast/dml.rs

+289-3
Original file line numberDiff line numberDiff line change
@@ -11,18 +11,304 @@
1111
// limitations under the License.
1212

1313
#[cfg(not(feature = "std"))]
14-
use alloc::{boxed::Box, vec::Vec};
14+
use alloc::{boxed::Box, string::String, vec::Vec};
1515

16+
use core::fmt::{self, Display};
1617
#[cfg(feature = "serde")]
1718
use serde::{Deserialize, Serialize};
1819
#[cfg(feature = "visitor")]
1920
use sqlparser_derive::{Visit, VisitMut};
2021

22+
pub use super::ddl::{ColumnDef, TableConstraint};
23+
2124
use super::{
22-
Expr, FromTable, Ident, InsertAliases, MysqlInsertPriority, ObjectName, OnInsert, OrderByExpr,
23-
Query, SelectItem, SqliteOnConflict, TableWithJoins,
25+
display_comma_separated, display_separated, Expr, FileFormat, FromTable, HiveDistributionStyle,
26+
HiveFormat, HiveIOFormat, HiveRowFormat, Ident, InsertAliases, MysqlInsertPriority, ObjectName,
27+
OnCommit, OnInsert, OrderByExpr, Query, SelectItem, SqlOption, SqliteOnConflict,
28+
TableWithJoins,
2429
};
2530

31+
/// CREATE INDEX statement.
32+
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
33+
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
34+
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
35+
pub struct CreateIndex {
36+
/// index name
37+
pub name: Option<ObjectName>,
38+
#[cfg_attr(feature = "visitor", visit(with = "visit_relation"))]
39+
pub table_name: ObjectName,
40+
pub using: Option<Ident>,
41+
pub columns: Vec<OrderByExpr>,
42+
pub unique: bool,
43+
pub concurrently: bool,
44+
pub if_not_exists: bool,
45+
pub include: Vec<Ident>,
46+
pub nulls_distinct: Option<bool>,
47+
pub predicate: Option<Expr>,
48+
}
49+
/// CREATE TABLE statement.
50+
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
51+
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
52+
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
53+
pub struct CreateTable {
54+
pub or_replace: bool,
55+
pub temporary: bool,
56+
pub external: bool,
57+
pub global: Option<bool>,
58+
pub if_not_exists: bool,
59+
pub transient: bool,
60+
/// Table name
61+
#[cfg_attr(feature = "visitor", visit(with = "visit_relation"))]
62+
pub name: ObjectName,
63+
/// Optional schema
64+
pub columns: Vec<ColumnDef>,
65+
pub constraints: Vec<TableConstraint>,
66+
pub hive_distribution: HiveDistributionStyle,
67+
pub hive_formats: Option<HiveFormat>,
68+
pub table_properties: Vec<SqlOption>,
69+
pub with_options: Vec<SqlOption>,
70+
pub file_format: Option<FileFormat>,
71+
pub location: Option<String>,
72+
pub query: Option<Box<Query>>,
73+
pub without_rowid: bool,
74+
pub like: Option<ObjectName>,
75+
pub clone: Option<ObjectName>,
76+
pub engine: Option<String>,
77+
pub comment: Option<String>,
78+
pub auto_increment_offset: Option<u32>,
79+
pub default_charset: Option<String>,
80+
pub collation: Option<String>,
81+
pub on_commit: Option<OnCommit>,
82+
/// ClickHouse "ON CLUSTER" clause:
83+
/// <https://clickhouse.com/docs/en/sql-reference/distributed-ddl/>
84+
pub on_cluster: Option<String>,
85+
/// ClickHouse "ORDER BY " clause. Note that omitted ORDER BY is different
86+
/// than empty (represented as ()), the latter meaning "no sorting".
87+
/// <https://clickhouse.com/docs/en/sql-reference/statements/create/table/>
88+
pub order_by: Option<Vec<Ident>>,
89+
/// BigQuery: A partition expression for the table.
90+
/// <https://cloud.google.com/bigquery/docs/reference/standard-sql/data-definition-language#partition_expression>
91+
pub partition_by: Option<Box<Expr>>,
92+
/// BigQuery: Table clustering column list.
93+
/// <https://cloud.google.com/bigquery/docs/reference/standard-sql/data-definition-language#table_option_list>
94+
pub cluster_by: Option<Vec<Ident>>,
95+
/// BigQuery: Table options list.
96+
/// <https://cloud.google.com/bigquery/docs/reference/standard-sql/data-definition-language#table_option_list>
97+
pub options: Option<Vec<SqlOption>>,
98+
/// SQLite "STRICT" clause.
99+
/// if the "STRICT" table-option keyword is added to the end, after the closing ")",
100+
/// then strict typing rules apply to that table.
101+
pub strict: bool,
102+
}
103+
104+
impl Display for CreateTable {
105+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
106+
// We want to allow the following options
107+
// Empty column list, allowed by PostgreSQL:
108+
// `CREATE TABLE t ()`
109+
// No columns provided for CREATE TABLE AS:
110+
// `CREATE TABLE t AS SELECT a from t2`
111+
// Columns provided for CREATE TABLE AS:
112+
// `CREATE TABLE t (a INT) AS SELECT a from t2`
113+
write!(
114+
f,
115+
"CREATE {or_replace}{external}{global}{temporary}{transient}TABLE {if_not_exists}{name}",
116+
or_replace = if self.or_replace { "OR REPLACE " } else { "" },
117+
external = if self.external { "EXTERNAL " } else { "" },
118+
global = self.global
119+
.map(|global| {
120+
if global {
121+
"GLOBAL "
122+
} else {
123+
"LOCAL "
124+
}
125+
})
126+
.unwrap_or(""),
127+
if_not_exists = if self.if_not_exists { "IF NOT EXISTS " } else { "" },
128+
temporary = if self.temporary { "TEMPORARY " } else { "" },
129+
transient = if self.transient { "TRANSIENT " } else { "" },
130+
name = self.name,
131+
)?;
132+
if let Some(on_cluster) = &self.on_cluster {
133+
write!(
134+
f,
135+
" ON CLUSTER {}",
136+
on_cluster.replace('{', "'{").replace('}', "}'")
137+
)?;
138+
}
139+
if !self.columns.is_empty() || !self.constraints.is_empty() {
140+
write!(f, " ({}", display_comma_separated(&self.columns))?;
141+
if !self.columns.is_empty() && !self.constraints.is_empty() {
142+
write!(f, ", ")?;
143+
}
144+
write!(f, "{})", display_comma_separated(&self.constraints))?;
145+
} else if self.query.is_none() && self.like.is_none() && self.clone.is_none() {
146+
// PostgreSQL allows `CREATE TABLE t ();`, but requires empty parens
147+
write!(f, " ()")?;
148+
}
149+
// Only for SQLite
150+
if self.without_rowid {
151+
write!(f, " WITHOUT ROWID")?;
152+
}
153+
154+
// Only for Hive
155+
if let Some(l) = &self.like {
156+
write!(f, " LIKE {l}")?;
157+
}
158+
159+
if let Some(c) = &self.clone {
160+
write!(f, " CLONE {c}")?;
161+
}
162+
163+
match &self.hive_distribution {
164+
HiveDistributionStyle::PARTITIONED { columns } => {
165+
write!(f, " PARTITIONED BY ({})", display_comma_separated(columns))?;
166+
}
167+
HiveDistributionStyle::CLUSTERED {
168+
columns,
169+
sorted_by,
170+
num_buckets,
171+
} => {
172+
write!(f, " CLUSTERED BY ({})", display_comma_separated(columns))?;
173+
if !sorted_by.is_empty() {
174+
write!(f, " SORTED BY ({})", display_comma_separated(sorted_by))?;
175+
}
176+
if *num_buckets > 0 {
177+
write!(f, " INTO {num_buckets} BUCKETS")?;
178+
}
179+
}
180+
HiveDistributionStyle::SKEWED {
181+
columns,
182+
on,
183+
stored_as_directories,
184+
} => {
185+
write!(
186+
f,
187+
" SKEWED BY ({})) ON ({})",
188+
display_comma_separated(columns),
189+
display_comma_separated(on)
190+
)?;
191+
if *stored_as_directories {
192+
write!(f, " STORED AS DIRECTORIES")?;
193+
}
194+
}
195+
_ => (),
196+
}
197+
198+
if let Some(HiveFormat {
199+
row_format,
200+
serde_properties,
201+
storage,
202+
location,
203+
}) = &self.hive_formats
204+
{
205+
match row_format {
206+
Some(HiveRowFormat::SERDE { class }) => write!(f, " ROW FORMAT SERDE '{class}'")?,
207+
Some(HiveRowFormat::DELIMITED { delimiters }) => {
208+
write!(f, " ROW FORMAT DELIMITED")?;
209+
if !delimiters.is_empty() {
210+
write!(f, " {}", display_separated(delimiters, " "))?;
211+
}
212+
}
213+
None => (),
214+
}
215+
match storage {
216+
Some(HiveIOFormat::IOF {
217+
input_format,
218+
output_format,
219+
}) => write!(
220+
f,
221+
" STORED AS INPUTFORMAT {input_format} OUTPUTFORMAT {output_format}"
222+
)?,
223+
Some(HiveIOFormat::FileFormat { format }) if !self.external => {
224+
write!(f, " STORED AS {format}")?
225+
}
226+
_ => (),
227+
}
228+
if let Some(serde_properties) = serde_properties.as_ref() {
229+
write!(
230+
f,
231+
" WITH SERDEPROPERTIES ({})",
232+
display_comma_separated(serde_properties)
233+
)?;
234+
}
235+
if !self.external {
236+
if let Some(loc) = location {
237+
write!(f, " LOCATION '{loc}'")?;
238+
}
239+
}
240+
}
241+
if self.external {
242+
if let Some(file_format) = self.file_format {
243+
write!(f, " STORED AS {file_format}")?;
244+
}
245+
write!(f, " LOCATION '{}'", self.location.as_ref().unwrap())?;
246+
}
247+
if !self.table_properties.is_empty() {
248+
write!(
249+
f,
250+
" TBLPROPERTIES ({})",
251+
display_comma_separated(&self.table_properties)
252+
)?;
253+
}
254+
if !self.with_options.is_empty() {
255+
write!(f, " WITH ({})", display_comma_separated(&self.with_options))?;
256+
}
257+
if let Some(engine) = &self.engine {
258+
write!(f, " ENGINE={engine}")?;
259+
}
260+
if let Some(comment) = &self.comment {
261+
write!(f, " COMMENT '{comment}'")?;
262+
}
263+
if let Some(auto_increment_offset) = self.auto_increment_offset {
264+
write!(f, " AUTO_INCREMENT {auto_increment_offset}")?;
265+
}
266+
if let Some(order_by) = &self.order_by {
267+
write!(f, " ORDER BY ({})", display_comma_separated(order_by))?;
268+
}
269+
if let Some(partition_by) = self.partition_by.as_ref() {
270+
write!(f, " PARTITION BY {partition_by}")?;
271+
}
272+
if let Some(cluster_by) = self.cluster_by.as_ref() {
273+
write!(
274+
f,
275+
" CLUSTER BY {}",
276+
display_comma_separated(cluster_by.as_slice())
277+
)?;
278+
}
279+
if let Some(options) = self.options.as_ref() {
280+
write!(
281+
f,
282+
" OPTIONS({})",
283+
display_comma_separated(options.as_slice())
284+
)?;
285+
}
286+
if let Some(query) = &self.query {
287+
write!(f, " AS {query}")?;
288+
}
289+
if let Some(default_charset) = &self.default_charset {
290+
write!(f, " DEFAULT CHARSET={default_charset}")?;
291+
}
292+
if let Some(collation) = &self.collation {
293+
write!(f, " COLLATE={collation}")?;
294+
}
295+
296+
if self.on_commit.is_some() {
297+
let on_commit = match self.on_commit {
298+
Some(OnCommit::DeleteRows) => "ON COMMIT DELETE ROWS",
299+
Some(OnCommit::PreserveRows) => "ON COMMIT PRESERVE ROWS",
300+
Some(OnCommit::Drop) => "ON COMMIT DROP",
301+
None => "",
302+
};
303+
write!(f, " {on_commit}")?;
304+
}
305+
if self.strict {
306+
write!(f, " STRICT")?;
307+
}
308+
Ok(())
309+
}
310+
}
311+
26312
/// INSERT statement.
27313
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
28314
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]

src/ast/helpers/stmt_create_table.rs

+5-4
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ use serde::{Deserialize, Serialize};
77
#[cfg(feature = "visitor")]
88
use sqlparser_derive::{Visit, VisitMut};
99

10+
use super::super::dml::CreateTable;
1011
use crate::ast::{
1112
ColumnDef, Expr, FileFormat, HiveDistributionStyle, HiveFormat, Ident, ObjectName, OnCommit,
1213
Query, SqlOption, Statement, TableConstraint,
@@ -263,7 +264,7 @@ impl CreateTableBuilder {
263264
}
264265

265266
pub fn build(self) -> Statement {
266-
Statement::CreateTable {
267+
Statement::CreateTable(CreateTable {
267268
or_replace: self.or_replace,
268269
temporary: self.temporary,
269270
external: self.external,
@@ -295,7 +296,7 @@ impl CreateTableBuilder {
295296
cluster_by: self.cluster_by,
296297
options: self.options,
297298
strict: self.strict,
298-
}
299+
})
299300
}
300301
}
301302

@@ -306,7 +307,7 @@ impl TryFrom<Statement> for CreateTableBuilder {
306307
// ownership.
307308
fn try_from(stmt: Statement) -> Result<Self, Self::Error> {
308309
match stmt {
309-
Statement::CreateTable {
310+
Statement::CreateTable(CreateTable {
310311
or_replace,
311312
temporary,
312313
external,
@@ -338,7 +339,7 @@ impl TryFrom<Statement> for CreateTableBuilder {
338339
cluster_by,
339340
options,
340341
strict,
341-
} => Ok(Self {
342+
}) => Ok(Self {
342343
or_replace,
343344
temporary,
344345
external,

0 commit comments

Comments
 (0)