Skip to content

Commit 009c8ff

Browse files
yoavcloudayman-sigma
authored andcommitted
Parse Snowflake COPY INTO <location> (apache#1669)
1 parent 64b3525 commit 009c8ff

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
@@ -2516,24 +2516,30 @@ pub enum Statement {
25162516
values: Vec<Option<String>>,
25172517
},
25182518
/// ```sql
2519-
/// COPY INTO
2519+
/// COPY INTO <table> | <location>
25202520
/// ```
2521-
/// See <https://docs.snowflake.com/en/sql-reference/sql/copy-into-table>
2521+
/// See:
2522+
/// <https://docs.snowflake.com/en/sql-reference/sql/copy-into-table>
2523+
/// <https://docs.snowflake.com/en/sql-reference/sql/copy-into-location>
2524+
///
25222525
/// Copy Into syntax available for Snowflake is different than the one implemented in
25232526
/// Postgres. Although they share common prefix, it is reasonable to implement them
25242527
/// in different enums. This can be refactored later once custom dialects
25252528
/// are allowed to have custom Statements.
25262529
CopyIntoSnowflake {
2530+
kind: CopyIntoSnowflakeKind,
25272531
into: ObjectName,
2528-
from_stage: ObjectName,
2529-
from_stage_alias: Option<Ident>,
2532+
from_obj: Option<ObjectName>,
2533+
from_obj_alias: Option<Ident>,
25302534
stage_params: StageParamsObject,
25312535
from_transformations: Option<Vec<StageLoadSelectItem>>,
2536+
from_query: Option<Box<Query>>,
25322537
files: Option<Vec<String>>,
25332538
pattern: Option<String>,
25342539
file_format: DataLoadingOptions,
25352540
copy_options: DataLoadingOptions,
25362541
validation_mode: Option<String>,
2542+
partition: Option<Box<Expr>>,
25372543
},
25382544
/// ```sql
25392545
/// CLOSE
@@ -5066,60 +5072,69 @@ impl fmt::Display for Statement {
50665072
Ok(())
50675073
}
50685074
Statement::CopyIntoSnowflake {
5075+
kind,
50695076
into,
5070-
from_stage,
5071-
from_stage_alias,
5077+
from_obj,
5078+
from_obj_alias,
50725079
stage_params,
50735080
from_transformations,
5081+
from_query,
50745082
files,
50755083
pattern,
50765084
file_format,
50775085
copy_options,
50785086
validation_mode,
5087+
partition,
50795088
} => {
50805089
write!(f, "COPY INTO {}", into)?;
5081-
if from_transformations.is_none() {
5082-
// Standard data load
5083-
write!(f, " FROM {}{}", from_stage, stage_params)?;
5084-
if from_stage_alias.as_ref().is_some() {
5085-
write!(f, " AS {}", from_stage_alias.as_ref().unwrap())?;
5086-
}
5087-
} else {
5090+
if let Some(from_transformations) = from_transformations {
50885091
// Data load with transformation
5089-
write!(
5090-
f,
5091-
" FROM (SELECT {} FROM {}{}",
5092-
display_separated(from_transformations.as_ref().unwrap(), ", "),
5093-
from_stage,
5094-
stage_params,
5095-
)?;
5096-
if from_stage_alias.as_ref().is_some() {
5097-
write!(f, " AS {}", from_stage_alias.as_ref().unwrap())?;
5092+
if let Some(from_stage) = from_obj {
5093+
write!(
5094+
f,
5095+
" FROM (SELECT {} FROM {}{}",
5096+
display_separated(from_transformations, ", "),
5097+
from_stage,
5098+
stage_params
5099+
)?;
5100+
}
5101+
if let Some(from_obj_alias) = from_obj_alias {
5102+
write!(f, " AS {}", from_obj_alias)?;
50985103
}
50995104
write!(f, ")")?;
5105+
} else if let Some(from_obj) = from_obj {
5106+
// Standard data load
5107+
write!(f, " FROM {}{}", from_obj, stage_params)?;
5108+
if let Some(from_obj_alias) = from_obj_alias {
5109+
write!(f, " AS {from_obj_alias}")?;
5110+
}
5111+
} else if let Some(from_query) = from_query {
5112+
// Data unload from query
5113+
write!(f, " FROM ({from_query})")?;
51005114
}
5101-
if files.is_some() {
5102-
write!(
5103-
f,
5104-
" FILES = ('{}')",
5105-
display_separated(files.as_ref().unwrap(), "', '")
5106-
)?;
5115+
5116+
if let Some(files) = files {
5117+
write!(f, " FILES = ('{}')", display_separated(files, "', '"))?;
5118+
}
5119+
if let Some(pattern) = pattern {
5120+
write!(f, " PATTERN = '{}'", pattern)?;
51075121
}
5108-
if pattern.is_some() {
5109-
write!(f, " PATTERN = '{}'", pattern.as_ref().unwrap())?;
5122+
if let Some(partition) = partition {
5123+
write!(f, " PARTITION BY {partition}")?;
51105124
}
51115125
if !file_format.options.is_empty() {
51125126
write!(f, " FILE_FORMAT=({})", file_format)?;
51135127
}
51145128
if !copy_options.options.is_empty() {
5115-
write!(f, " COPY_OPTIONS=({})", copy_options)?;
5129+
match kind {
5130+
CopyIntoSnowflakeKind::Table => {
5131+
write!(f, " COPY_OPTIONS=({})", copy_options)?
5132+
}
5133+
CopyIntoSnowflakeKind::Location => write!(f, " {copy_options}")?,
5134+
}
51165135
}
5117-
if validation_mode.is_some() {
5118-
write!(
5119-
f,
5120-
" VALIDATION_MODE = {}",
5121-
validation_mode.as_ref().unwrap()
5122-
)?;
5136+
if let Some(validation_mode) = validation_mode {
5137+
write!(f, " VALIDATION_MODE = {}", validation_mode)?;
51235138
}
51245139
Ok(())
51255140
}
@@ -8561,6 +8576,19 @@ impl Display for StorageSerializationPolicy {
85618576
}
85628577
}
85638578

8579+
/// Variants of the Snowflake `COPY INTO` statement
8580+
#[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
8581+
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
8582+
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
8583+
pub enum CopyIntoSnowflakeKind {
8584+
/// Loads data from files to a table
8585+
/// See: <https://docs.snowflake.com/en/sql-reference/sql/copy-into-table>
8586+
Table,
8587+
/// Unloads data from a table or query to external files
8588+
/// See: <https://docs.snowflake.com/en/sql-reference/sql/copy-into-location>
8589+
Location,
8590+
}
8591+
85648592
#[cfg(test)]
85658593
mod tests {
85668594
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)