Skip to content

Commit 751dc5a

Browse files
authored
Parse Snowflake COPY INTO <location> (#1669)
1 parent 486b29f commit 751dc5a

File tree

5 files changed

+381
-185
lines changed

5 files changed

+381
-185
lines changed

src/ast/helpers/stmt_data_loading.rs

+4-6
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ pub enum DataLoadingOptionType {
5858
STRING,
5959
BOOLEAN,
6060
ENUM,
61+
NUMBER,
6162
}
6263

6364
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
@@ -128,12 +129,9 @@ impl fmt::Display for DataLoadingOption {
128129
DataLoadingOptionType::STRING => {
129130
write!(f, "{}='{}'", self.option_name, self.value)?;
130131
}
131-
DataLoadingOptionType::ENUM => {
132-
// single quote is omitted
133-
write!(f, "{}={}", self.option_name, self.value)?;
134-
}
135-
DataLoadingOptionType::BOOLEAN => {
136-
// single quote is omitted
132+
DataLoadingOptionType::ENUM
133+
| DataLoadingOptionType::BOOLEAN
134+
| DataLoadingOptionType::NUMBER => {
137135
write!(f, "{}={}", self.option_name, self.value)?;
138136
}
139137
}

src/ast/mod.rs

+65-37
Original file line numberDiff line numberDiff line change
@@ -2498,24 +2498,30 @@ pub enum Statement {
24982498
values: Vec<Option<String>>,
24992499
},
25002500
/// ```sql
2501-
/// COPY INTO
2501+
/// COPY INTO <table> | <location>
25022502
/// ```
2503-
/// See <https://docs.snowflake.com/en/sql-reference/sql/copy-into-table>
2503+
/// See:
2504+
/// <https://docs.snowflake.com/en/sql-reference/sql/copy-into-table>
2505+
/// <https://docs.snowflake.com/en/sql-reference/sql/copy-into-location>
2506+
///
25042507
/// Copy Into syntax available for Snowflake is different than the one implemented in
25052508
/// Postgres. Although they share common prefix, it is reasonable to implement them
25062509
/// in different enums. This can be refactored later once custom dialects
25072510
/// are allowed to have custom Statements.
25082511
CopyIntoSnowflake {
2512+
kind: CopyIntoSnowflakeKind,
25092513
into: ObjectName,
2510-
from_stage: ObjectName,
2511-
from_stage_alias: Option<Ident>,
2514+
from_obj: Option<ObjectName>,
2515+
from_obj_alias: Option<Ident>,
25122516
stage_params: StageParamsObject,
25132517
from_transformations: Option<Vec<StageLoadSelectItem>>,
2518+
from_query: Option<Box<Query>>,
25142519
files: Option<Vec<String>>,
25152520
pattern: Option<String>,
25162521
file_format: DataLoadingOptions,
25172522
copy_options: DataLoadingOptions,
25182523
validation_mode: Option<String>,
2524+
partition: Option<Box<Expr>>,
25192525
},
25202526
/// ```sql
25212527
/// CLOSE
@@ -5048,60 +5054,69 @@ impl fmt::Display for Statement {
50485054
Ok(())
50495055
}
50505056
Statement::CopyIntoSnowflake {
5057+
kind,
50515058
into,
5052-
from_stage,
5053-
from_stage_alias,
5059+
from_obj,
5060+
from_obj_alias,
50545061
stage_params,
50555062
from_transformations,
5063+
from_query,
50565064
files,
50575065
pattern,
50585066
file_format,
50595067
copy_options,
50605068
validation_mode,
5069+
partition,
50615070
} => {
50625071
write!(f, "COPY INTO {}", into)?;
5063-
if from_transformations.is_none() {
5064-
// Standard data load
5065-
write!(f, " FROM {}{}", from_stage, stage_params)?;
5066-
if from_stage_alias.as_ref().is_some() {
5067-
write!(f, " AS {}", from_stage_alias.as_ref().unwrap())?;
5068-
}
5069-
} else {
5072+
if let Some(from_transformations) = from_transformations {
50705073
// Data load with transformation
5071-
write!(
5072-
f,
5073-
" FROM (SELECT {} FROM {}{}",
5074-
display_separated(from_transformations.as_ref().unwrap(), ", "),
5075-
from_stage,
5076-
stage_params,
5077-
)?;
5078-
if from_stage_alias.as_ref().is_some() {
5079-
write!(f, " AS {}", from_stage_alias.as_ref().unwrap())?;
5074+
if let Some(from_stage) = from_obj {
5075+
write!(
5076+
f,
5077+
" FROM (SELECT {} FROM {}{}",
5078+
display_separated(from_transformations, ", "),
5079+
from_stage,
5080+
stage_params
5081+
)?;
5082+
}
5083+
if let Some(from_obj_alias) = from_obj_alias {
5084+
write!(f, " AS {}", from_obj_alias)?;
50805085
}
50815086
write!(f, ")")?;
5087+
} else if let Some(from_obj) = from_obj {
5088+
// Standard data load
5089+
write!(f, " FROM {}{}", from_obj, stage_params)?;
5090+
if let Some(from_obj_alias) = from_obj_alias {
5091+
write!(f, " AS {from_obj_alias}")?;
5092+
}
5093+
} else if let Some(from_query) = from_query {
5094+
// Data unload from query
5095+
write!(f, " FROM ({from_query})")?;
50825096
}
5083-
if files.is_some() {
5084-
write!(
5085-
f,
5086-
" FILES = ('{}')",
5087-
display_separated(files.as_ref().unwrap(), "', '")
5088-
)?;
5097+
5098+
if let Some(files) = files {
5099+
write!(f, " FILES = ('{}')", display_separated(files, "', '"))?;
5100+
}
5101+
if let Some(pattern) = pattern {
5102+
write!(f, " PATTERN = '{}'", pattern)?;
50895103
}
5090-
if pattern.is_some() {
5091-
write!(f, " PATTERN = '{}'", pattern.as_ref().unwrap())?;
5104+
if let Some(partition) = partition {
5105+
write!(f, " PARTITION BY {partition}")?;
50925106
}
50935107
if !file_format.options.is_empty() {
50945108
write!(f, " FILE_FORMAT=({})", file_format)?;
50955109
}
50965110
if !copy_options.options.is_empty() {
5097-
write!(f, " COPY_OPTIONS=({})", copy_options)?;
5111+
match kind {
5112+
CopyIntoSnowflakeKind::Table => {
5113+
write!(f, " COPY_OPTIONS=({})", copy_options)?
5114+
}
5115+
CopyIntoSnowflakeKind::Location => write!(f, " {copy_options}")?,
5116+
}
50985117
}
5099-
if validation_mode.is_some() {
5100-
write!(
5101-
f,
5102-
" VALIDATION_MODE = {}",
5103-
validation_mode.as_ref().unwrap()
5104-
)?;
5118+
if let Some(validation_mode) = validation_mode {
5119+
write!(f, " VALIDATION_MODE = {}", validation_mode)?;
51055120
}
51065121
Ok(())
51075122
}
@@ -8543,6 +8558,19 @@ impl Display for StorageSerializationPolicy {
85438558
}
85448559
}
85458560

8561+
/// Variants of the Snowflake `COPY INTO` statement
8562+
#[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
8563+
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
8564+
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
8565+
pub enum CopyIntoSnowflakeKind {
8566+
/// Loads data from files to a table
8567+
/// See: <https://docs.snowflake.com/en/sql-reference/sql/copy-into-table>
8568+
Table,
8569+
/// Unloads data from a table or query to external files
8570+
/// See: <https://docs.snowflake.com/en/sql-reference/sql/copy-into-location>
8571+
Location,
8572+
}
8573+
85468574
#[cfg(test)]
85478575
mod tests {
85488576
use super::*;

src/ast/spans.rs

+5-2
Original file line numberDiff line numberDiff line change
@@ -333,15 +333,18 @@ impl Spanned for Statement {
333333
} => source.span(),
334334
Statement::CopyIntoSnowflake {
335335
into: _,
336-
from_stage: _,
337-
from_stage_alias: _,
336+
from_obj: _,
337+
from_obj_alias: _,
338338
stage_params: _,
339339
from_transformations: _,
340340
files: _,
341341
pattern: _,
342342
file_format: _,
343343
copy_options: _,
344344
validation_mode: _,
345+
kind: _,
346+
from_query: _,
347+
partition: _,
345348
} => Span::empty(),
346349
Statement::Close { cursor } => match cursor {
347350
CloseCursor::All => Span::empty(),

0 commit comments

Comments
 (0)