sqlparser/dialect/
snowflake.rs1// Licensed to the Apache Software Foundation (ASF) under one
2// or more contributor license agreements. See the NOTICE file
3// distributed with this work for additional information
4// regarding copyright ownership. The ASF licenses this file
5// to you under the Apache License, Version 2.0 (the
6// "License"); you may not use this file except in compliance
7// with the License. You may obtain a copy of the License at
8//
9// http://www.apache.org/licenses/LICENSE-2.0
10//
11// Unless required by applicable law or agreed to in writing,
12// software distributed under the License is distributed on an
13// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14// KIND, either express or implied. See the License for the
15// specific language governing permissions and limitations
16// under the License.
17
18#[cfg(not(feature = "std"))]
19use crate::alloc::string::ToString;
20use crate::ast::helpers::attached_token::AttachedToken;
21use crate::ast::helpers::key_value_options::{
22 KeyValueOption, KeyValueOptionKind, KeyValueOptions, KeyValueOptionsDelimiter,
23};
24use crate::ast::helpers::stmt_create_database::CreateDatabaseBuilder;
25use crate::ast::helpers::stmt_create_table::CreateTableBuilder;
26use crate::ast::helpers::stmt_data_loading::{
27 FileStagingCommand, StageLoadSelectItem, StageLoadSelectItemKind, StageParamsObject,
28};
29use crate::ast::{
30 AlterTable, AlterTableOperation, AlterTableType, CatalogSyncNamespaceMode, ColumnOption,
31 ColumnPolicy, ColumnPolicyProperty, ContactEntry, CopyIntoSnowflakeKind, CreateTable,
32 CreateTableLikeKind, DollarQuotedString, Ident, IdentityParameters, IdentityProperty,
33 IdentityPropertyFormatKind, IdentityPropertyKind, IdentityPropertyOrder, InitializeKind,
34 ObjectName, ObjectNamePart, RefreshModeKind, RowAccessPolicy, ShowObjects, SqlOption,
35 Statement, StorageSerializationPolicy, TagsColumnOption, Value, WrappedCollection,
36};
37use crate::dialect::{Dialect, Precedence};
38use crate::keywords::Keyword;
39use crate::parser::{IsOptional, Parser, ParserError};
40use crate::tokenizer::Token;
41#[cfg(not(feature = "std"))]
42use alloc::boxed::Box;
43#[cfg(not(feature = "std"))]
44use alloc::string::String;
45#[cfg(not(feature = "std"))]
46use alloc::vec::Vec;
47#[cfg(not(feature = "std"))]
48use alloc::{format, vec};
49
50use super::keywords::RESERVED_FOR_IDENTIFIER;
51
52const RESERVED_KEYWORDS_FOR_SELECT_ITEM_OPERATOR: [Keyword; 1] = [Keyword::CONNECT_BY_ROOT];
53
54// See: <https://docs.snowflake.com/en/sql-reference/reserved-keywords>
55const RESERVED_KEYWORDS_FOR_TABLE_FACTOR: &[Keyword] = &[
56 Keyword::ALL,
57 Keyword::ALTER,
58 Keyword::AND,
59 Keyword::ANY,
60 Keyword::AS,
61 Keyword::BETWEEN,
62 Keyword::BY,
63 Keyword::CHECK,
64 Keyword::COLUMN,
65 Keyword::CONNECT,
66 Keyword::CREATE,
67 Keyword::CROSS,
68 Keyword::CURRENT,
69 Keyword::DELETE,
70 Keyword::DISTINCT,
71 Keyword::DROP,
72 Keyword::ELSE,
73 Keyword::EXISTS,
74 Keyword::FOLLOWING,
75 Keyword::FOR,
76 Keyword::FROM,
77 Keyword::FULL,
78 Keyword::GRANT,
79 Keyword::GROUP,
80 Keyword::HAVING,
81 Keyword::ILIKE,
82 Keyword::IN,
83 Keyword::INCREMENT,
84 Keyword::INNER,
85 Keyword::INSERT,
86 Keyword::INTERSECT,
87 Keyword::INTO,
88 Keyword::IS,
89 Keyword::JOIN,
90 Keyword::LEFT,
91 Keyword::LIKE,
92 Keyword::MINUS,
93 Keyword::NATURAL,
94 Keyword::NOT,
95 Keyword::NULL,
96 Keyword::OF,
97 Keyword::ON,
98 Keyword::OR,
99 Keyword::ORDER,
100 Keyword::QUALIFY,
101 Keyword::REGEXP,
102 Keyword::REVOKE,
103 Keyword::RIGHT,
104 Keyword::RLIKE,
105 Keyword::ROW,
106 Keyword::ROWS,
107 Keyword::SAMPLE,
108 Keyword::SELECT,
109 Keyword::SET,
110 Keyword::SOME,
111 Keyword::START,
112 Keyword::TABLE,
113 Keyword::TABLESAMPLE,
114 Keyword::THEN,
115 Keyword::TO,
116 Keyword::TRIGGER,
117 Keyword::UNION,
118 Keyword::UNIQUE,
119 Keyword::UPDATE,
120 Keyword::USING,
121 Keyword::VALUES,
122 Keyword::WHEN,
123 Keyword::WHENEVER,
124 Keyword::WHERE,
125 Keyword::WINDOW,
126 Keyword::WITH,
127];
128
129/// A [`Dialect`] for [Snowflake](https://www.snowflake.com/)
130#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
131#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
132pub struct SnowflakeDialect;
133
134impl Dialect for SnowflakeDialect {
135 // see https://docs.snowflake.com/en/sql-reference/identifiers-syntax.html
136 fn is_identifier_start(&self, ch: char) -> bool {
137 ch.is_ascii_lowercase() || ch.is_ascii_uppercase() || ch == '_'
138 }
139
140 fn supports_projection_trailing_commas(&self) -> bool {
141 true
142 }
143
144 fn supports_from_trailing_commas(&self) -> bool {
145 true
146 }
147
148 // Snowflake supports double-dot notation when the schema name is not specified
149 // In this case the default PUBLIC schema is used
150 //
151 // see https://docs.snowflake.com/en/sql-reference/name-resolution#resolution-when-schema-omitted-double-dot-notation
152 fn supports_object_name_double_dot_notation(&self) -> bool {
153 true
154 }
155
156 fn is_identifier_part(&self, ch: char) -> bool {
157 ch.is_ascii_lowercase()
158 || ch.is_ascii_uppercase()
159 || ch.is_ascii_digit()
160 || ch == '$'
161 || ch == '_'
162 }
163
164 // See https://cloud.google.com/bigquery/docs/reference/standard-sql/lexical#escape_sequences
165 fn supports_string_literal_backslash_escape(&self) -> bool {
166 true
167 }
168
169 fn supports_within_after_array_aggregation(&self) -> bool {
170 true
171 }
172
173 /// See <https://docs.snowflake.com/en/sql-reference/constructs/where#joins-in-the-where-clause>
174 fn supports_outer_join_operator(&self) -> bool {
175 true
176 }
177
178 fn supports_connect_by(&self) -> bool {
179 true
180 }
181
182 /// See <https://docs.snowflake.com/en/sql-reference/sql/execute-immediate>
183 fn supports_execute_immediate(&self) -> bool {
184 true
185 }
186
187 fn supports_match_recognize(&self) -> bool {
188 true
189 }
190
191 // Snowflake uses this syntax for "object constants" (the values of which
192 // are not actually required to be constants).
193 //
194 // https://docs.snowflake.com/en/sql-reference/data-types-semistructured#label-object-constant
195 fn supports_dictionary_syntax(&self) -> bool {
196 true
197 }
198
199 // Snowflake doesn't document this but `FIRST_VALUE(arg, { IGNORE | RESPECT } NULLS)`
200 // works (i.e. inside the argument list instead of after).
201 fn supports_window_function_null_treatment_arg(&self) -> bool {
202 true
203 }
204
205 /// See [doc](https://docs.snowflake.com/en/sql-reference/sql/set#syntax)
206 fn supports_parenthesized_set_variables(&self) -> bool {
207 true
208 }
209
210 /// See [doc](https://docs.snowflake.com/en/sql-reference/sql/comment)
211 fn supports_comment_on(&self) -> bool {
212 true
213 }
214
215 /// See [doc](https://docs.snowflake.com/en/sql-reference/functions/extract)
216 fn supports_extract_comma_syntax(&self) -> bool {
217 true
218 }
219
220 /// See [doc](https://docs.snowflake.com/en/sql-reference/functions/flatten)
221 fn supports_subquery_as_function_arg(&self) -> bool {
222 true
223 }
224
225 /// See [doc](https://docs.snowflake.com/en/sql-reference/sql/create-view#optional-parameters)
226 fn supports_create_view_comment_syntax(&self) -> bool {
227 true
228 }
229
230 /// See [doc](https://docs.snowflake.com/en/sql-reference/data-types-semistructured#array)
231 fn supports_array_typedef_without_element_type(&self) -> bool {
232 true
233 }
234
235 /// See [doc](https://docs.snowflake.com/en/sql-reference/constructs/from)
236 fn supports_parens_around_table_factor(&self) -> bool {
237 true
238 }
239
240 /// See [doc](https://docs.snowflake.com/en/sql-reference/constructs/values)
241 fn supports_values_as_table_factor(&self) -> bool {
242 true
243 }
244
245 fn parse_statement(&self, parser: &mut Parser) -> Option<Result<Statement, ParserError>> {
246 if parser.parse_keyword(Keyword::BEGIN) {
247 return Some(parser.parse_begin_exception_end());
248 }
249
250 if parser.parse_keywords(&[Keyword::ALTER, Keyword::DYNAMIC, Keyword::TABLE]) {
251 // ALTER DYNAMIC TABLE
252 return Some(parse_alter_dynamic_table(parser));
253 }
254
255 if parser.parse_keywords(&[Keyword::ALTER, Keyword::EXTERNAL, Keyword::TABLE]) {
256 // ALTER EXTERNAL TABLE
257 return Some(parse_alter_external_table(parser));
258 }
259
260 if parser.parse_keywords(&[Keyword::ALTER, Keyword::SESSION]) {
261 // ALTER SESSION
262 let set = match parser.parse_one_of_keywords(&[Keyword::SET, Keyword::UNSET]) {
263 Some(Keyword::SET) => true,
264 Some(Keyword::UNSET) => false,
265 _ => return Some(parser.expected("SET or UNSET", parser.peek_token())),
266 };
267 return Some(parse_alter_session(parser, set));
268 }
269
270 if parser.parse_keyword(Keyword::CREATE) {
271 // possibly CREATE STAGE
272 //[ OR REPLACE ]
273 let or_replace = parser.parse_keywords(&[Keyword::OR, Keyword::REPLACE]);
274 // LOCAL | GLOBAL
275 let global = match parser.parse_one_of_keywords(&[Keyword::LOCAL, Keyword::GLOBAL]) {
276 Some(Keyword::LOCAL) => Some(false),
277 Some(Keyword::GLOBAL) => Some(true),
278 _ => None,
279 };
280
281 let dynamic = parser.parse_keyword(Keyword::DYNAMIC);
282
283 let mut temporary = false;
284 let mut volatile = false;
285 let mut transient = false;
286 let mut iceberg = false;
287
288 match parser.parse_one_of_keywords(&[
289 Keyword::TEMP,
290 Keyword::TEMPORARY,
291 Keyword::VOLATILE,
292 Keyword::TRANSIENT,
293 Keyword::ICEBERG,
294 ]) {
295 Some(Keyword::TEMP | Keyword::TEMPORARY) => temporary = true,
296 Some(Keyword::VOLATILE) => volatile = true,
297 Some(Keyword::TRANSIENT) => transient = true,
298 Some(Keyword::ICEBERG) => iceberg = true,
299 _ => {}
300 }
301
302 if parser.parse_keyword(Keyword::STAGE) {
303 // OK - this is CREATE STAGE statement
304 return Some(parse_create_stage(or_replace, temporary, parser));
305 } else if parser.parse_keyword(Keyword::TABLE) {
306 return Some(
307 parse_create_table(
308 or_replace, global, temporary, volatile, transient, iceberg, dynamic,
309 parser,
310 )
311 .map(Into::into),
312 );
313 } else if parser.parse_keyword(Keyword::DATABASE) {
314 return Some(parse_create_database(or_replace, transient, parser));
315 } else {
316 // need to go back with the cursor
317 let mut back = 1;
318 if or_replace {
319 back += 2
320 }
321 if temporary {
322 back += 1
323 }
324 for _i in 0..back {
325 parser.prev_token();
326 }
327 }
328 }
329 if parser.parse_keywords(&[Keyword::COPY, Keyword::INTO]) {
330 // COPY INTO
331 return Some(parse_copy_into(parser));
332 }
333
334 if let Some(kw) = parser.parse_one_of_keywords(&[
335 Keyword::LIST,
336 Keyword::LS,
337 Keyword::REMOVE,
338 Keyword::RM,
339 ]) {
340 return Some(parse_file_staging_command(kw, parser));
341 }
342
343 if parser.parse_keyword(Keyword::SHOW) {
344 let terse = parser.parse_keyword(Keyword::TERSE);
345 if parser.parse_keyword(Keyword::OBJECTS) {
346 return Some(parse_show_objects(terse, parser));
347 }
348 //Give back Keyword::TERSE
349 if terse {
350 parser.prev_token();
351 }
352 //Give back Keyword::SHOW
353 parser.prev_token();
354 }
355
356 None
357 }
358
359 fn parse_column_option(
360 &self,
361 parser: &mut Parser,
362 ) -> Result<Option<Result<Option<ColumnOption>, ParserError>>, ParserError> {
363 parser.maybe_parse(|parser| {
364 let with = parser.parse_keyword(Keyword::WITH);
365
366 if parser.parse_keyword(Keyword::IDENTITY) {
367 Ok(parse_identity_property(parser)
368 .map(|p| Some(ColumnOption::Identity(IdentityPropertyKind::Identity(p)))))
369 } else if parser.parse_keyword(Keyword::AUTOINCREMENT) {
370 Ok(parse_identity_property(parser).map(|p| {
371 Some(ColumnOption::Identity(IdentityPropertyKind::Autoincrement(
372 p,
373 )))
374 }))
375 } else if parser.parse_keywords(&[Keyword::MASKING, Keyword::POLICY]) {
376 Ok(parse_column_policy_property(parser, with)
377 .map(|p| Some(ColumnOption::Policy(ColumnPolicy::MaskingPolicy(p)))))
378 } else if parser.parse_keywords(&[Keyword::PROJECTION, Keyword::POLICY]) {
379 Ok(parse_column_policy_property(parser, with)
380 .map(|p| Some(ColumnOption::Policy(ColumnPolicy::ProjectionPolicy(p)))))
381 } else if parser.parse_keywords(&[Keyword::TAG]) {
382 Ok(parse_column_tags(parser, with).map(|p| Some(ColumnOption::Tags(p))))
383 } else {
384 Err(ParserError::ParserError("not found match".to_string()))
385 }
386 })
387 }
388
389 fn get_next_precedence(&self, parser: &Parser) -> Option<Result<u8, ParserError>> {
390 let token = parser.peek_token();
391 // Snowflake supports the `:` cast operator unlike other dialects
392 match token.token {
393 Token::Colon => Some(Ok(self.prec_value(Precedence::DoubleColon))),
394 _ => None,
395 }
396 }
397
398 fn describe_requires_table_keyword(&self) -> bool {
399 true
400 }
401
402 fn allow_extract_custom(&self) -> bool {
403 true
404 }
405
406 fn allow_extract_single_quotes(&self) -> bool {
407 true
408 }
409
410 /// Snowflake expects the `LIKE` option before the `IN` option,
411 /// for example: <https://docs.snowflake.com/en/sql-reference/sql/show-views#syntax>
412 fn supports_show_like_before_in(&self) -> bool {
413 true
414 }
415
416 fn supports_left_associative_joins_without_parens(&self) -> bool {
417 false
418 }
419
420 fn is_reserved_for_identifier(&self, kw: Keyword) -> bool {
421 // Unreserve some keywords that Snowflake accepts as identifiers
422 // See: https://docs.snowflake.com/en/sql-reference/reserved-keywords
423 if matches!(kw, Keyword::INTERVAL) {
424 false
425 } else {
426 RESERVED_FOR_IDENTIFIER.contains(&kw)
427 }
428 }
429
430 fn supports_partiql(&self) -> bool {
431 true
432 }
433
434 fn is_column_alias(&self, kw: &Keyword, parser: &mut Parser) -> bool {
435 match kw {
436 // The following keywords can be considered an alias as long as
437 // they are not followed by other tokens that may change their meaning
438 // e.g. `SELECT * EXCEPT (col1) FROM tbl`
439 Keyword::EXCEPT
440 // e.g. `INSERT INTO t SELECT 1 RETURNING *`
441 | Keyword::RETURNING if !matches!(parser.peek_token_ref().token, Token::Comma | Token::EOF) =>
442 {
443 false
444 }
445
446 // e.g. `SELECT 1 LIMIT 5` - not an alias
447 // e.g. `SELECT 1 OFFSET 5 ROWS` - not an alias
448 Keyword::LIMIT | Keyword::OFFSET if peek_for_limit_options(parser) => false,
449
450 // `FETCH` can be considered an alias as long as it's not followed by `FIRST`` or `NEXT`
451 // which would give it a different meanings, for example:
452 // `SELECT 1 FETCH FIRST 10 ROWS` - not an alias
453 // `SELECT 1 FETCH 10` - not an alias
454 Keyword::FETCH if parser.peek_one_of_keywords(&[Keyword::FIRST, Keyword::NEXT]).is_some()
455 || peek_for_limit_options(parser) =>
456 {
457 false
458 }
459
460 // Reserved keywords by the Snowflake dialect, which seem to be less strictive
461 // than what is listed in `keywords::RESERVED_FOR_COLUMN_ALIAS`. The following
462 // keywords were tested with the this statement: `SELECT 1 <KW>`.
463 Keyword::FROM
464 | Keyword::GROUP
465 | Keyword::HAVING
466 | Keyword::INTERSECT
467 | Keyword::INTO
468 | Keyword::MINUS
469 | Keyword::ORDER
470 | Keyword::SELECT
471 | Keyword::UNION
472 | Keyword::WHERE
473 | Keyword::WITH => false,
474
475 // Any other word is considered an alias
476 _ => true,
477 }
478 }
479
480 fn is_table_alias(&self, kw: &Keyword, parser: &mut Parser) -> bool {
481 match kw {
482 // The following keywords can be considered an alias as long as
483 // they are not followed by other tokens that may change their meaning
484 Keyword::RETURNING
485 | Keyword::INNER
486 | Keyword::USING
487 | Keyword::PIVOT
488 | Keyword::UNPIVOT
489 | Keyword::EXCEPT
490 | Keyword::MATCH_RECOGNIZE
491 if !matches!(parser.peek_token_ref().token, Token::SemiColon | Token::EOF) =>
492 {
493 false
494 }
495
496 // `LIMIT` can be considered an alias as long as it's not followed by a value. For example:
497 // `SELECT * FROM tbl LIMIT WHERE 1=1` - alias
498 // `SELECT * FROM tbl LIMIT 3` - not an alias
499 Keyword::LIMIT | Keyword::OFFSET if peek_for_limit_options(parser) => false,
500
501 // `FETCH` can be considered an alias as long as it's not followed by `FIRST`` or `NEXT`
502 // which would give it a different meanings, for example:
503 // `SELECT * FROM tbl FETCH FIRST 10 ROWS` - not an alias
504 // `SELECT * FROM tbl FETCH 10` - not an alias
505 Keyword::FETCH
506 if parser
507 .peek_one_of_keywords(&[Keyword::FIRST, Keyword::NEXT])
508 .is_some()
509 || peek_for_limit_options(parser) =>
510 {
511 false
512 }
513
514 // All sorts of join-related keywords can be considered aliases unless additional
515 // keywords change their meaning.
516 Keyword::RIGHT | Keyword::LEFT | Keyword::SEMI | Keyword::ANTI
517 if parser
518 .peek_one_of_keywords(&[Keyword::JOIN, Keyword::OUTER])
519 .is_some() =>
520 {
521 false
522 }
523
524 Keyword::GLOBAL if parser.peek_keyword(Keyword::FULL) => false,
525
526 // Reserved keywords by the Snowflake dialect, which seem to be less strictive
527 // than what is listed in `keywords::RESERVED_FOR_TABLE_ALIAS`. The following
528 // keywords were tested with the this statement: `SELECT <KW>.* FROM tbl <KW>`.
529 Keyword::WITH
530 | Keyword::ORDER
531 | Keyword::SELECT
532 | Keyword::WHERE
533 | Keyword::GROUP
534 | Keyword::HAVING
535 | Keyword::LATERAL
536 | Keyword::UNION
537 | Keyword::INTERSECT
538 | Keyword::MINUS
539 | Keyword::ON
540 | Keyword::JOIN
541 | Keyword::INNER
542 | Keyword::CROSS
543 | Keyword::FULL
544 | Keyword::LEFT
545 | Keyword::RIGHT
546 | Keyword::NATURAL
547 | Keyword::USING
548 | Keyword::ASOF
549 | Keyword::MATCH_CONDITION
550 | Keyword::SET
551 | Keyword::QUALIFY
552 | Keyword::FOR
553 | Keyword::START
554 | Keyword::CONNECT
555 | Keyword::SAMPLE
556 | Keyword::TABLESAMPLE
557 | Keyword::FROM => false,
558
559 // Any other word is considered an alias
560 _ => true,
561 }
562 }
563
564 fn is_table_factor(&self, kw: &Keyword, parser: &mut Parser) -> bool {
565 match kw {
566 Keyword::LIMIT if peek_for_limit_options(parser) => false,
567 // Table function
568 Keyword::TABLE if matches!(parser.peek_token_ref().token, Token::LParen) => true,
569 _ => !RESERVED_KEYWORDS_FOR_TABLE_FACTOR.contains(kw),
570 }
571 }
572
573 /// See: <https://docs.snowflake.com/en/sql-reference/constructs/at-before>
574 fn supports_table_versioning(&self) -> bool {
575 true
576 }
577
578 /// See: <https://docs.snowflake.com/en/sql-reference/constructs/group-by>
579 fn supports_group_by_expr(&self) -> bool {
580 true
581 }
582
583 /// See: <https://docs.snowflake.com/en/sql-reference/constructs/connect-by>
584 fn get_reserved_keywords_for_select_item_operator(&self) -> &[Keyword] {
585 &RESERVED_KEYWORDS_FOR_SELECT_ITEM_OPERATOR
586 }
587
588 fn supports_space_separated_column_options(&self) -> bool {
589 true
590 }
591
592 fn supports_comma_separated_drop_column_list(&self) -> bool {
593 true
594 }
595
596 fn is_identifier_generating_function_name(
597 &self,
598 ident: &Ident,
599 name_parts: &[ObjectNamePart],
600 ) -> bool {
601 ident.quote_style.is_none()
602 && ident.value.to_lowercase() == "identifier"
603 && !name_parts
604 .iter()
605 .any(|p| matches!(p, ObjectNamePart::Function(_)))
606 }
607
608 // For example: `SELECT IDENTIFIER('alias1').* FROM tbl AS alias1`
609 fn supports_select_expr_star(&self) -> bool {
610 true
611 }
612
613 fn supports_select_wildcard_exclude(&self) -> bool {
614 true
615 }
616
617 fn supports_semantic_view_table_factor(&self) -> bool {
618 true
619 }
620
621 /// See <https://docs.snowflake.com/en/sql-reference/sql/select#parameters>
622 fn supports_select_wildcard_replace(&self) -> bool {
623 true
624 }
625
626 /// See <https://docs.snowflake.com/en/sql-reference/sql/select#parameters>
627 fn supports_select_wildcard_ilike(&self) -> bool {
628 true
629 }
630
631 /// See <https://docs.snowflake.com/en/sql-reference/sql/select#parameters>
632 fn supports_select_wildcard_rename(&self) -> bool {
633 true
634 }
635}
636
637// Peeks ahead to identify tokens that are expected after
638// a LIMIT/FETCH keyword.
639fn peek_for_limit_options(parser: &Parser) -> bool {
640 match &parser.peek_token_ref().token {
641 Token::Number(_, _) | Token::Placeholder(_) => true,
642 Token::SingleQuotedString(val) if val.is_empty() => true,
643 Token::DollarQuotedString(DollarQuotedString { value, .. }) if value.is_empty() => true,
644 Token::Word(w) if w.keyword == Keyword::NULL => true,
645 _ => false,
646 }
647}
648
649fn parse_file_staging_command(kw: Keyword, parser: &mut Parser) -> Result<Statement, ParserError> {
650 let stage = parse_snowflake_stage_name(parser)?;
651 let pattern = if parser.parse_keyword(Keyword::PATTERN) {
652 parser.expect_token(&Token::Eq)?;
653 Some(parser.parse_literal_string()?)
654 } else {
655 None
656 };
657
658 match kw {
659 Keyword::LIST | Keyword::LS => Ok(Statement::List(FileStagingCommand { stage, pattern })),
660 Keyword::REMOVE | Keyword::RM => {
661 Ok(Statement::Remove(FileStagingCommand { stage, pattern }))
662 }
663 _ => Err(ParserError::ParserError(
664 "unexpected stage command, expecting LIST, LS, REMOVE or RM".to_string(),
665 )),
666 }
667}
668
669/// Parse snowflake alter dynamic table.
670/// <https://docs.snowflake.com/en/sql-reference/sql/alter-table>
671fn parse_alter_dynamic_table(parser: &mut Parser) -> Result<Statement, ParserError> {
672 // Use parse_object_name(true) to support IDENTIFIER() function
673 let table_name = parser.parse_object_name(true)?;
674
675 // Parse the operation (REFRESH, SUSPEND, or RESUME)
676 let operation = if parser.parse_keyword(Keyword::REFRESH) {
677 AlterTableOperation::Refresh { subpath: None }
678 } else if parser.parse_keyword(Keyword::SUSPEND) {
679 AlterTableOperation::Suspend
680 } else if parser.parse_keyword(Keyword::RESUME) {
681 AlterTableOperation::Resume
682 } else {
683 return parser.expected(
684 "REFRESH, SUSPEND, or RESUME after ALTER DYNAMIC TABLE",
685 parser.peek_token(),
686 );
687 };
688
689 let end_token = if parser.peek_token_ref().token == Token::SemiColon {
690 parser.peek_token_ref().clone()
691 } else {
692 parser.get_current_token().clone()
693 };
694
695 Ok(Statement::AlterTable(AlterTable {
696 name: table_name,
697 if_exists: false,
698 only: false,
699 operations: vec![operation],
700 location: None,
701 on_cluster: None,
702 table_type: Some(AlterTableType::Dynamic),
703 end_token: AttachedToken(end_token),
704 }))
705}
706
707/// Parse snowflake alter external table.
708/// <https://docs.snowflake.com/en/sql-reference/sql/alter-external-table>
709fn parse_alter_external_table(parser: &mut Parser) -> Result<Statement, ParserError> {
710 let if_exists = parser.parse_keywords(&[Keyword::IF, Keyword::EXISTS]);
711 let table_name = parser.parse_object_name(true)?;
712
713 // Parse the operation (REFRESH for now)
714 let operation = if parser.parse_keyword(Keyword::REFRESH) {
715 // Optional subpath for refreshing specific partitions
716 let subpath = match parser.peek_token().token {
717 Token::SingleQuotedString(s) => {
718 parser.next_token();
719 Some(s)
720 }
721 _ => None,
722 };
723 AlterTableOperation::Refresh { subpath }
724 } else {
725 return parser.expected("REFRESH after ALTER EXTERNAL TABLE", parser.peek_token());
726 };
727
728 let end_token = if parser.peek_token_ref().token == Token::SemiColon {
729 parser.peek_token_ref().clone()
730 } else {
731 parser.get_current_token().clone()
732 };
733
734 Ok(Statement::AlterTable(AlterTable {
735 name: table_name,
736 if_exists,
737 only: false,
738 operations: vec![operation],
739 location: None,
740 on_cluster: None,
741 table_type: Some(AlterTableType::External),
742 end_token: AttachedToken(end_token),
743 }))
744}
745
746/// Parse snowflake alter session.
747/// <https://docs.snowflake.com/en/sql-reference/sql/alter-session>
748fn parse_alter_session(parser: &mut Parser, set: bool) -> Result<Statement, ParserError> {
749 let session_options = parse_session_options(parser, set)?;
750 Ok(Statement::AlterSession {
751 set,
752 session_params: KeyValueOptions {
753 options: session_options,
754 delimiter: KeyValueOptionsDelimiter::Space,
755 },
756 })
757}
758
759/// Parse snowflake create table statement.
760/// <https://docs.snowflake.com/en/sql-reference/sql/create-table>
761/// <https://docs.snowflake.com/en/sql-reference/sql/create-iceberg-table>
762#[allow(clippy::too_many_arguments)]
763pub fn parse_create_table(
764 or_replace: bool,
765 global: Option<bool>,
766 temporary: bool,
767 volatile: bool,
768 transient: bool,
769 iceberg: bool,
770 dynamic: bool,
771 parser: &mut Parser,
772) -> Result<CreateTable, ParserError> {
773 let if_not_exists = parser.parse_keywords(&[Keyword::IF, Keyword::NOT, Keyword::EXISTS]);
774 let table_name = parser.parse_object_name(false)?;
775
776 let mut builder = CreateTableBuilder::new(table_name)
777 .or_replace(or_replace)
778 .if_not_exists(if_not_exists)
779 .temporary(temporary)
780 .transient(transient)
781 .volatile(volatile)
782 .iceberg(iceberg)
783 .global(global)
784 .dynamic(dynamic)
785 .hive_formats(None);
786
787 // Snowflake does not enforce order of the parameters in the statement. The parser needs to
788 // parse the statement in a loop.
789 //
790 // "CREATE TABLE x COPY GRANTS (c INT)" and "CREATE TABLE x (c INT) COPY GRANTS" are both
791 // accepted by Snowflake
792
793 let mut plain_options = vec![];
794
795 loop {
796 let next_token = parser.next_token();
797 match &next_token.token {
798 Token::Word(word) => match word.keyword {
799 Keyword::COPY => {
800 parser.expect_keyword_is(Keyword::GRANTS)?;
801 builder = builder.copy_grants(true);
802 }
803 Keyword::COMMENT => {
804 // Rewind the COMMENT keyword
805 parser.prev_token();
806 if let Some(comment_def) = parser.parse_optional_inline_comment()? {
807 plain_options.push(SqlOption::Comment(comment_def))
808 }
809 }
810 Keyword::AS => {
811 let query = parser.parse_query()?;
812 builder = builder.query(Some(query));
813 }
814 Keyword::CLONE => {
815 let clone = parser.parse_object_name(false).ok();
816 builder = builder.clone_clause(clone);
817 }
818 Keyword::LIKE => {
819 let name = parser.parse_object_name(false)?;
820 builder = builder.like(Some(CreateTableLikeKind::Plain(
821 crate::ast::CreateTableLike {
822 name,
823 defaults: None,
824 },
825 )));
826 }
827 Keyword::CLUSTER => {
828 parser.expect_keyword_is(Keyword::BY)?;
829 parser.expect_token(&Token::LParen)?;
830 let cluster_by = Some(WrappedCollection::Parentheses(
831 parser.parse_comma_separated(|p| p.parse_expr())?,
832 ));
833 parser.expect_token(&Token::RParen)?;
834
835 builder = builder.cluster_by(cluster_by)
836 }
837 Keyword::ENABLE_SCHEMA_EVOLUTION => {
838 parser.expect_token(&Token::Eq)?;
839 builder = builder.enable_schema_evolution(Some(parser.parse_boolean_string()?));
840 }
841 Keyword::CHANGE_TRACKING => {
842 parser.expect_token(&Token::Eq)?;
843 builder = builder.change_tracking(Some(parser.parse_boolean_string()?));
844 }
845 Keyword::DATA_RETENTION_TIME_IN_DAYS => {
846 parser.expect_token(&Token::Eq)?;
847 let data_retention_time_in_days = parser.parse_literal_uint()?;
848 builder =
849 builder.data_retention_time_in_days(Some(data_retention_time_in_days));
850 }
851 Keyword::MAX_DATA_EXTENSION_TIME_IN_DAYS => {
852 parser.expect_token(&Token::Eq)?;
853 let max_data_extension_time_in_days = parser.parse_literal_uint()?;
854 builder = builder
855 .max_data_extension_time_in_days(Some(max_data_extension_time_in_days));
856 }
857 Keyword::DEFAULT_DDL_COLLATION => {
858 parser.expect_token(&Token::Eq)?;
859 let default_ddl_collation = parser.parse_literal_string()?;
860 builder = builder.default_ddl_collation(Some(default_ddl_collation));
861 }
862 // WITH is optional, we just verify that next token is one of the expected ones and
863 // fallback to the default match statement
864 Keyword::WITH => {
865 parser.expect_one_of_keywords(&[
866 Keyword::AGGREGATION,
867 Keyword::TAG,
868 Keyword::ROW,
869 ])?;
870 parser.prev_token();
871 }
872 Keyword::AGGREGATION => {
873 parser.expect_keyword_is(Keyword::POLICY)?;
874 let aggregation_policy = parser.parse_object_name(false)?;
875 builder = builder.with_aggregation_policy(Some(aggregation_policy));
876 }
877 Keyword::ROW => {
878 parser.expect_keywords(&[Keyword::ACCESS, Keyword::POLICY])?;
879 let policy = parser.parse_object_name(false)?;
880 parser.expect_keyword_is(Keyword::ON)?;
881 parser.expect_token(&Token::LParen)?;
882 let columns = parser.parse_comma_separated(|p| p.parse_identifier())?;
883 parser.expect_token(&Token::RParen)?;
884
885 builder =
886 builder.with_row_access_policy(Some(RowAccessPolicy::new(policy, columns)))
887 }
888 Keyword::TAG => {
889 parser.expect_token(&Token::LParen)?;
890 let tags = parser.parse_comma_separated(Parser::parse_tag)?;
891 parser.expect_token(&Token::RParen)?;
892 builder = builder.with_tags(Some(tags));
893 }
894 Keyword::ON if parser.parse_keyword(Keyword::COMMIT) => {
895 let on_commit = Some(parser.parse_create_table_on_commit()?);
896 builder = builder.on_commit(on_commit);
897 }
898 Keyword::EXTERNAL_VOLUME => {
899 parser.expect_token(&Token::Eq)?;
900 builder.external_volume = Some(parser.parse_literal_string()?);
901 }
902 Keyword::CATALOG => {
903 parser.expect_token(&Token::Eq)?;
904 builder.catalog = Some(parser.parse_literal_string()?);
905 }
906 Keyword::BASE_LOCATION => {
907 parser.expect_token(&Token::Eq)?;
908 builder.base_location = Some(parser.parse_literal_string()?);
909 }
910 Keyword::CATALOG_SYNC => {
911 parser.expect_token(&Token::Eq)?;
912 builder.catalog_sync = Some(parser.parse_literal_string()?);
913 }
914 Keyword::STORAGE_SERIALIZATION_POLICY => {
915 parser.expect_token(&Token::Eq)?;
916
917 builder.storage_serialization_policy =
918 Some(parse_storage_serialization_policy(parser)?);
919 }
920 Keyword::IF if parser.parse_keywords(&[Keyword::NOT, Keyword::EXISTS]) => {
921 builder = builder.if_not_exists(true);
922 }
923 Keyword::TARGET_LAG => {
924 parser.expect_token(&Token::Eq)?;
925 let target_lag = parser.parse_literal_string()?;
926 builder = builder.target_lag(Some(target_lag));
927 }
928 Keyword::WAREHOUSE => {
929 parser.expect_token(&Token::Eq)?;
930 let warehouse = parser.parse_identifier()?;
931 builder = builder.warehouse(Some(warehouse));
932 }
933 Keyword::AT | Keyword::BEFORE => {
934 parser.prev_token();
935 let version = parser.maybe_parse_table_version()?;
936 builder = builder.version(version);
937 }
938 Keyword::REFRESH_MODE => {
939 parser.expect_token(&Token::Eq)?;
940 let refresh_mode = match parser.parse_one_of_keywords(&[
941 Keyword::AUTO,
942 Keyword::FULL,
943 Keyword::INCREMENTAL,
944 ]) {
945 Some(Keyword::AUTO) => Some(RefreshModeKind::Auto),
946 Some(Keyword::FULL) => Some(RefreshModeKind::Full),
947 Some(Keyword::INCREMENTAL) => Some(RefreshModeKind::Incremental),
948 _ => return parser.expected("AUTO, FULL or INCREMENTAL", next_token),
949 };
950 builder = builder.refresh_mode(refresh_mode);
951 }
952 Keyword::INITIALIZE => {
953 parser.expect_token(&Token::Eq)?;
954 let initialize = match parser
955 .parse_one_of_keywords(&[Keyword::ON_CREATE, Keyword::ON_SCHEDULE])
956 {
957 Some(Keyword::ON_CREATE) => Some(InitializeKind::OnCreate),
958 Some(Keyword::ON_SCHEDULE) => Some(InitializeKind::OnSchedule),
959 _ => return parser.expected("ON_CREATE or ON_SCHEDULE", next_token),
960 };
961 builder = builder.initialize(initialize);
962 }
963 Keyword::REQUIRE if parser.parse_keyword(Keyword::USER) => {
964 builder = builder.require_user(true);
965 }
966 _ => {
967 return parser.expected("end of statement", next_token);
968 }
969 },
970 Token::LParen => {
971 parser.prev_token();
972 let (columns, constraints) = parser.parse_columns()?;
973 builder = builder.columns(columns).constraints(constraints);
974 }
975 Token::EOF => {
976 break;
977 }
978 Token::SemiColon => {
979 parser.prev_token();
980 break;
981 }
982 _ => {
983 return parser.expected("end of statement", next_token);
984 }
985 }
986 }
987 let table_options = if !plain_options.is_empty() {
988 crate::ast::CreateTableOptions::Plain(plain_options)
989 } else {
990 crate::ast::CreateTableOptions::None
991 };
992
993 builder = builder.table_options(table_options);
994
995 if iceberg && builder.base_location.is_none() {
996 return Err(ParserError::ParserError(
997 "BASE_LOCATION is required for ICEBERG tables".to_string(),
998 ));
999 }
1000
1001 Ok(builder.build())
1002}
1003
1004/// Parse snowflake create database statement.
1005/// <https://docs.snowflake.com/en/sql-reference/sql/create-database>
1006pub fn parse_create_database(
1007 or_replace: bool,
1008 transient: bool,
1009 parser: &mut Parser,
1010) -> Result<Statement, ParserError> {
1011 let if_not_exists = parser.parse_keywords(&[Keyword::IF, Keyword::NOT, Keyword::EXISTS]);
1012 let name = parser.parse_object_name(false)?;
1013
1014 let mut builder = CreateDatabaseBuilder::new(name)
1015 .or_replace(or_replace)
1016 .transient(transient)
1017 .if_not_exists(if_not_exists);
1018
1019 loop {
1020 let next_token = parser.next_token();
1021 match &next_token.token {
1022 Token::Word(word) => match word.keyword {
1023 Keyword::CLONE => {
1024 builder = builder.clone_clause(Some(parser.parse_object_name(false)?));
1025 }
1026 Keyword::DATA_RETENTION_TIME_IN_DAYS => {
1027 parser.expect_token(&Token::Eq)?;
1028 builder =
1029 builder.data_retention_time_in_days(Some(parser.parse_literal_uint()?));
1030 }
1031 Keyword::MAX_DATA_EXTENSION_TIME_IN_DAYS => {
1032 parser.expect_token(&Token::Eq)?;
1033 builder =
1034 builder.max_data_extension_time_in_days(Some(parser.parse_literal_uint()?));
1035 }
1036 Keyword::EXTERNAL_VOLUME => {
1037 parser.expect_token(&Token::Eq)?;
1038 builder = builder.external_volume(Some(parser.parse_literal_string()?));
1039 }
1040 Keyword::CATALOG => {
1041 parser.expect_token(&Token::Eq)?;
1042 builder = builder.catalog(Some(parser.parse_literal_string()?));
1043 }
1044 Keyword::REPLACE_INVALID_CHARACTERS => {
1045 parser.expect_token(&Token::Eq)?;
1046 builder =
1047 builder.replace_invalid_characters(Some(parser.parse_boolean_string()?));
1048 }
1049 Keyword::DEFAULT_DDL_COLLATION => {
1050 parser.expect_token(&Token::Eq)?;
1051 builder = builder.default_ddl_collation(Some(parser.parse_literal_string()?));
1052 }
1053 Keyword::STORAGE_SERIALIZATION_POLICY => {
1054 parser.expect_token(&Token::Eq)?;
1055 let policy = parse_storage_serialization_policy(parser)?;
1056 builder = builder.storage_serialization_policy(Some(policy));
1057 }
1058 Keyword::COMMENT => {
1059 parser.expect_token(&Token::Eq)?;
1060 builder = builder.comment(Some(parser.parse_literal_string()?));
1061 }
1062 Keyword::CATALOG_SYNC => {
1063 parser.expect_token(&Token::Eq)?;
1064 builder = builder.catalog_sync(Some(parser.parse_literal_string()?));
1065 }
1066 Keyword::CATALOG_SYNC_NAMESPACE_FLATTEN_DELIMITER => {
1067 parser.expect_token(&Token::Eq)?;
1068 builder = builder.catalog_sync_namespace_flatten_delimiter(Some(
1069 parser.parse_literal_string()?,
1070 ));
1071 }
1072 Keyword::CATALOG_SYNC_NAMESPACE_MODE => {
1073 parser.expect_token(&Token::Eq)?;
1074 let mode =
1075 match parser.parse_one_of_keywords(&[Keyword::NEST, Keyword::FLATTEN]) {
1076 Some(Keyword::NEST) => CatalogSyncNamespaceMode::Nest,
1077 Some(Keyword::FLATTEN) => CatalogSyncNamespaceMode::Flatten,
1078 _ => {
1079 return parser.expected("NEST or FLATTEN", next_token);
1080 }
1081 };
1082 builder = builder.catalog_sync_namespace_mode(Some(mode));
1083 }
1084 Keyword::WITH => {
1085 if parser.parse_keyword(Keyword::TAG) {
1086 parser.expect_token(&Token::LParen)?;
1087 let tags = parser.parse_comma_separated(Parser::parse_tag)?;
1088 parser.expect_token(&Token::RParen)?;
1089 builder = builder.with_tags(Some(tags));
1090 } else if parser.parse_keyword(Keyword::CONTACT) {
1091 parser.expect_token(&Token::LParen)?;
1092 let contacts = parser.parse_comma_separated(|p| {
1093 let purpose = p.parse_identifier()?.value;
1094 p.expect_token(&Token::Eq)?;
1095 let contact = p.parse_identifier()?.value;
1096 Ok(ContactEntry { purpose, contact })
1097 })?;
1098 parser.expect_token(&Token::RParen)?;
1099 builder = builder.with_contacts(Some(contacts));
1100 } else {
1101 return parser.expected("TAG or CONTACT", next_token);
1102 }
1103 }
1104 _ => return parser.expected("end of statement", next_token),
1105 },
1106 Token::SemiColon | Token::EOF => break,
1107 _ => return parser.expected("end of statement", next_token),
1108 }
1109 }
1110 Ok(builder.build())
1111}
1112
1113pub fn parse_storage_serialization_policy(
1114 parser: &mut Parser,
1115) -> Result<StorageSerializationPolicy, ParserError> {
1116 let next_token = parser.next_token();
1117 match &next_token.token {
1118 Token::Word(w) => match w.keyword {
1119 Keyword::COMPATIBLE => Ok(StorageSerializationPolicy::Compatible),
1120 Keyword::OPTIMIZED => Ok(StorageSerializationPolicy::Optimized),
1121 _ => parser.expected("storage_serialization_policy", next_token),
1122 },
1123 _ => parser.expected("storage_serialization_policy", next_token),
1124 }
1125}
1126
1127pub fn parse_create_stage(
1128 or_replace: bool,
1129 temporary: bool,
1130 parser: &mut Parser,
1131) -> Result<Statement, ParserError> {
1132 //[ IF NOT EXISTS ]
1133 let if_not_exists = parser.parse_keywords(&[Keyword::IF, Keyword::NOT, Keyword::EXISTS]);
1134 let name = parser.parse_object_name(false)?;
1135 let mut directory_table_params = Vec::new();
1136 let mut file_format = Vec::new();
1137 let mut copy_options = Vec::new();
1138 let mut comment = None;
1139
1140 // [ internalStageParams | externalStageParams ]
1141 let stage_params = parse_stage_params(parser)?;
1142
1143 // [ directoryTableParams ]
1144 if parser.parse_keyword(Keyword::DIRECTORY) {
1145 parser.expect_token(&Token::Eq)?;
1146 directory_table_params = parser.parse_key_value_options(true, &[])?.options;
1147 }
1148
1149 // [ file_format]
1150 if parser.parse_keyword(Keyword::FILE_FORMAT) {
1151 parser.expect_token(&Token::Eq)?;
1152 file_format = parser.parse_key_value_options(true, &[])?.options;
1153 }
1154
1155 // [ copy_options ]
1156 if parser.parse_keyword(Keyword::COPY_OPTIONS) {
1157 parser.expect_token(&Token::Eq)?;
1158 copy_options = parser.parse_key_value_options(true, &[])?.options;
1159 }
1160
1161 // [ comment ]
1162 if parser.parse_keyword(Keyword::COMMENT) {
1163 parser.expect_token(&Token::Eq)?;
1164 comment = Some(parser.parse_comment_value()?);
1165 }
1166
1167 Ok(Statement::CreateStage {
1168 or_replace,
1169 temporary,
1170 if_not_exists,
1171 name,
1172 stage_params,
1173 directory_table_params: KeyValueOptions {
1174 options: directory_table_params,
1175 delimiter: KeyValueOptionsDelimiter::Space,
1176 },
1177 file_format: KeyValueOptions {
1178 options: file_format,
1179 delimiter: KeyValueOptionsDelimiter::Space,
1180 },
1181 copy_options: KeyValueOptions {
1182 options: copy_options,
1183 delimiter: KeyValueOptionsDelimiter::Space,
1184 },
1185 comment,
1186 })
1187}
1188
1189pub fn parse_stage_name_identifier(parser: &mut Parser) -> Result<Ident, ParserError> {
1190 let mut ident = String::new();
1191 while let Some(next_token) = parser.next_token_no_skip() {
1192 match &next_token.token {
1193 Token::Whitespace(_) | Token::SemiColon => break,
1194 Token::Period => {
1195 parser.prev_token();
1196 break;
1197 }
1198 Token::RParen => {
1199 parser.prev_token();
1200 break;
1201 }
1202 Token::AtSign => ident.push('@'),
1203 Token::Tilde => ident.push('~'),
1204 Token::Mod => ident.push('%'),
1205 Token::Div => ident.push('/'),
1206 Token::Plus => ident.push('+'),
1207 Token::Minus => ident.push('-'),
1208 Token::Number(n, _) => ident.push_str(n),
1209 Token::Word(w) => ident.push_str(&w.to_string()),
1210 _ => return parser.expected("stage name identifier", parser.peek_token()),
1211 }
1212 }
1213 Ok(Ident::new(ident))
1214}
1215
1216pub fn parse_snowflake_stage_name(parser: &mut Parser) -> Result<ObjectName, ParserError> {
1217 match parser.next_token().token {
1218 Token::AtSign => {
1219 parser.prev_token();
1220 let mut idents = vec![];
1221 loop {
1222 idents.push(parse_stage_name_identifier(parser)?);
1223 if !parser.consume_token(&Token::Period) {
1224 break;
1225 }
1226 }
1227 Ok(ObjectName::from(idents))
1228 }
1229 _ => {
1230 parser.prev_token();
1231 Ok(parser.parse_object_name(false)?)
1232 }
1233 }
1234}
1235
1236/// Parses a `COPY INTO` statement. Snowflake has two variants, `COPY INTO <table>`
1237/// and `COPY INTO <location>` which have different syntax.
1238pub fn parse_copy_into(parser: &mut Parser) -> Result<Statement, ParserError> {
1239 let kind = match parser.peek_token().token {
1240 // Indicates an internal stage
1241 Token::AtSign => CopyIntoSnowflakeKind::Location,
1242 // Indicates an external stage, i.e. s3://, gcs:// or azure://
1243 Token::SingleQuotedString(s) if s.contains("://") => CopyIntoSnowflakeKind::Location,
1244 _ => CopyIntoSnowflakeKind::Table,
1245 };
1246
1247 let mut files: Vec<String> = vec![];
1248 let mut from_transformations: Option<Vec<StageLoadSelectItemKind>> = None;
1249 let mut from_stage_alias = None;
1250 let mut from_stage = None;
1251 let mut stage_params = StageParamsObject {
1252 url: None,
1253 encryption: KeyValueOptions {
1254 options: vec![],
1255 delimiter: KeyValueOptionsDelimiter::Space,
1256 },
1257 endpoint: None,
1258 storage_integration: None,
1259 credentials: KeyValueOptions {
1260 options: vec![],
1261 delimiter: KeyValueOptionsDelimiter::Space,
1262 },
1263 };
1264 let mut from_query = None;
1265 let mut partition = None;
1266 let mut file_format = Vec::new();
1267 let mut pattern = None;
1268 let mut validation_mode = None;
1269 let mut copy_options = Vec::new();
1270
1271 let into: ObjectName = parse_snowflake_stage_name(parser)?;
1272 if kind == CopyIntoSnowflakeKind::Location {
1273 stage_params = parse_stage_params(parser)?;
1274 }
1275
1276 let into_columns = match &parser.peek_token().token {
1277 Token::LParen => Some(parser.parse_parenthesized_column_list(IsOptional::Optional, true)?),
1278 _ => None,
1279 };
1280
1281 parser.expect_keyword_is(Keyword::FROM)?;
1282 match parser.next_token().token {
1283 Token::LParen if kind == CopyIntoSnowflakeKind::Table => {
1284 // Data load with transformations
1285 parser.expect_keyword_is(Keyword::SELECT)?;
1286 from_transformations = parse_select_items_for_data_load(parser)?;
1287
1288 parser.expect_keyword_is(Keyword::FROM)?;
1289 from_stage = Some(parse_snowflake_stage_name(parser)?);
1290 stage_params = parse_stage_params(parser)?;
1291
1292 // Parse an optional alias
1293 from_stage_alias = parser
1294 .maybe_parse_table_alias()?
1295 .map(|table_alias| table_alias.name);
1296 parser.expect_token(&Token::RParen)?;
1297 }
1298 Token::LParen if kind == CopyIntoSnowflakeKind::Location => {
1299 // Data unload with a query
1300 from_query = Some(parser.parse_query()?);
1301 parser.expect_token(&Token::RParen)?;
1302 }
1303 _ => {
1304 parser.prev_token();
1305 from_stage = Some(parse_snowflake_stage_name(parser)?);
1306 stage_params = parse_stage_params(parser)?;
1307
1308 // as
1309 from_stage_alias = if parser.parse_keyword(Keyword::AS) {
1310 Some(match parser.next_token().token {
1311 Token::Word(w) => Ok(Ident::new(w.value)),
1312 _ => parser.expected("stage alias", parser.peek_token()),
1313 }?)
1314 } else {
1315 None
1316 };
1317 }
1318 }
1319
1320 loop {
1321 // FILE_FORMAT
1322 if parser.parse_keyword(Keyword::FILE_FORMAT) {
1323 parser.expect_token(&Token::Eq)?;
1324 file_format = parser.parse_key_value_options(true, &[])?.options;
1325 // PARTITION BY
1326 } else if parser.parse_keywords(&[Keyword::PARTITION, Keyword::BY]) {
1327 partition = Some(Box::new(parser.parse_expr()?))
1328 // FILES
1329 } else if parser.parse_keyword(Keyword::FILES) {
1330 parser.expect_token(&Token::Eq)?;
1331 parser.expect_token(&Token::LParen)?;
1332 let mut continue_loop = true;
1333 while continue_loop {
1334 continue_loop = false;
1335 let next_token = parser.next_token();
1336 match next_token.token {
1337 Token::SingleQuotedString(s) => files.push(s),
1338 _ => parser.expected("file token", next_token)?,
1339 };
1340 if parser.next_token().token.eq(&Token::Comma) {
1341 continue_loop = true;
1342 } else {
1343 parser.prev_token(); // not a comma, need to go back
1344 }
1345 }
1346 parser.expect_token(&Token::RParen)?;
1347 // PATTERN
1348 } else if parser.parse_keyword(Keyword::PATTERN) {
1349 parser.expect_token(&Token::Eq)?;
1350 let next_token = parser.next_token();
1351 pattern = Some(match next_token.token {
1352 Token::SingleQuotedString(s) => s,
1353 _ => parser.expected("pattern", next_token)?,
1354 });
1355 // VALIDATION MODE
1356 } else if parser.parse_keyword(Keyword::VALIDATION_MODE) {
1357 parser.expect_token(&Token::Eq)?;
1358 validation_mode = Some(parser.next_token().token.to_string());
1359 // COPY OPTIONS
1360 } else if parser.parse_keyword(Keyword::COPY_OPTIONS) {
1361 parser.expect_token(&Token::Eq)?;
1362 copy_options = parser.parse_key_value_options(true, &[])?.options;
1363 } else {
1364 match parser.next_token().token {
1365 Token::SemiColon | Token::EOF => break,
1366 Token::Comma => continue,
1367 // In `COPY INTO <location>` the copy options do not have a shared key
1368 // like in `COPY INTO <table>`
1369 Token::Word(key) => copy_options.push(parser.parse_key_value_option(&key)?),
1370 _ => return parser.expected("another copy option, ; or EOF'", parser.peek_token()),
1371 }
1372 }
1373 }
1374
1375 Ok(Statement::CopyIntoSnowflake {
1376 kind,
1377 into,
1378 into_columns,
1379 from_obj: from_stage,
1380 from_obj_alias: from_stage_alias,
1381 stage_params,
1382 from_transformations,
1383 from_query,
1384 files: if files.is_empty() { None } else { Some(files) },
1385 pattern,
1386 file_format: KeyValueOptions {
1387 options: file_format,
1388 delimiter: KeyValueOptionsDelimiter::Space,
1389 },
1390 copy_options: KeyValueOptions {
1391 options: copy_options,
1392 delimiter: KeyValueOptionsDelimiter::Space,
1393 },
1394 validation_mode,
1395 partition,
1396 })
1397}
1398
1399fn parse_select_items_for_data_load(
1400 parser: &mut Parser,
1401) -> Result<Option<Vec<StageLoadSelectItemKind>>, ParserError> {
1402 let mut select_items: Vec<StageLoadSelectItemKind> = vec![];
1403 loop {
1404 match parser.maybe_parse(parse_select_item_for_data_load)? {
1405 // [<alias>.]$<file_col_num>[.<element>] [ , [<alias>.]$<file_col_num>[.<element>] ... ]
1406 Some(item) => select_items.push(StageLoadSelectItemKind::StageLoadSelectItem(item)),
1407 // Fallback, try to parse a standard SQL select item
1408 None => select_items.push(StageLoadSelectItemKind::SelectItem(
1409 parser.parse_select_item()?,
1410 )),
1411 }
1412 if matches!(parser.peek_token_ref().token, Token::Comma) {
1413 parser.advance_token();
1414 } else {
1415 break;
1416 }
1417 }
1418 Ok(Some(select_items))
1419}
1420
1421fn parse_select_item_for_data_load(
1422 parser: &mut Parser,
1423) -> Result<StageLoadSelectItem, ParserError> {
1424 let mut alias: Option<Ident> = None;
1425 let mut file_col_num: i32 = 0;
1426 let mut element: Option<Ident> = None;
1427 let mut item_as: Option<Ident> = None;
1428
1429 let next_token = parser.next_token();
1430 match next_token.token {
1431 Token::Placeholder(w) => {
1432 file_col_num = w.to_string().split_off(1).parse::<i32>().map_err(|e| {
1433 ParserError::ParserError(format!("Could not parse '{w}' as i32: {e}"))
1434 })?;
1435 Ok(())
1436 }
1437 Token::Word(w) => {
1438 alias = Some(Ident::new(w.value));
1439 Ok(())
1440 }
1441 _ => parser.expected("alias or file_col_num", next_token),
1442 }?;
1443
1444 if alias.is_some() {
1445 parser.expect_token(&Token::Period)?;
1446 // now we get col_num token
1447 let col_num_token = parser.next_token();
1448 match col_num_token.token {
1449 Token::Placeholder(w) => {
1450 file_col_num = w.to_string().split_off(1).parse::<i32>().map_err(|e| {
1451 ParserError::ParserError(format!("Could not parse '{w}' as i32: {e}"))
1452 })?;
1453 Ok(())
1454 }
1455 _ => parser.expected("file_col_num", col_num_token),
1456 }?;
1457 }
1458
1459 // try extracting optional element
1460 match parser.next_token().token {
1461 Token::Colon => {
1462 // parse element
1463 element = Some(Ident::new(match parser.next_token().token {
1464 Token::Word(w) => Ok(w.value),
1465 _ => parser.expected("file_col_num", parser.peek_token()),
1466 }?));
1467 }
1468 _ => {
1469 // element not present move back
1470 parser.prev_token();
1471 }
1472 }
1473
1474 // as
1475 if parser.parse_keyword(Keyword::AS) {
1476 item_as = Some(match parser.next_token().token {
1477 Token::Word(w) => Ok(Ident::new(w.value)),
1478 _ => parser.expected("column item alias", parser.peek_token()),
1479 }?);
1480 }
1481
1482 Ok(StageLoadSelectItem {
1483 alias,
1484 file_col_num,
1485 element,
1486 item_as,
1487 })
1488}
1489
1490fn parse_stage_params(parser: &mut Parser) -> Result<StageParamsObject, ParserError> {
1491 let (mut url, mut storage_integration, mut endpoint) = (None, None, None);
1492 let mut encryption: KeyValueOptions = KeyValueOptions {
1493 options: vec![],
1494 delimiter: KeyValueOptionsDelimiter::Space,
1495 };
1496 let mut credentials: KeyValueOptions = KeyValueOptions {
1497 options: vec![],
1498 delimiter: KeyValueOptionsDelimiter::Space,
1499 };
1500
1501 // URL
1502 if parser.parse_keyword(Keyword::URL) {
1503 parser.expect_token(&Token::Eq)?;
1504 url = Some(match parser.next_token().token {
1505 Token::SingleQuotedString(word) => Ok(word),
1506 _ => parser.expected("a URL statement", parser.peek_token()),
1507 }?)
1508 }
1509
1510 // STORAGE INTEGRATION
1511 if parser.parse_keyword(Keyword::STORAGE_INTEGRATION) {
1512 parser.expect_token(&Token::Eq)?;
1513 storage_integration = Some(parser.next_token().token.to_string());
1514 }
1515
1516 // ENDPOINT
1517 if parser.parse_keyword(Keyword::ENDPOINT) {
1518 parser.expect_token(&Token::Eq)?;
1519 endpoint = Some(match parser.next_token().token {
1520 Token::SingleQuotedString(word) => Ok(word),
1521 _ => parser.expected("an endpoint statement", parser.peek_token()),
1522 }?)
1523 }
1524
1525 // CREDENTIALS
1526 if parser.parse_keyword(Keyword::CREDENTIALS) {
1527 parser.expect_token(&Token::Eq)?;
1528 credentials = KeyValueOptions {
1529 options: parser.parse_key_value_options(true, &[])?.options,
1530 delimiter: KeyValueOptionsDelimiter::Space,
1531 };
1532 }
1533
1534 // ENCRYPTION
1535 if parser.parse_keyword(Keyword::ENCRYPTION) {
1536 parser.expect_token(&Token::Eq)?;
1537 encryption = KeyValueOptions {
1538 options: parser.parse_key_value_options(true, &[])?.options,
1539 delimiter: KeyValueOptionsDelimiter::Space,
1540 };
1541 }
1542
1543 Ok(StageParamsObject {
1544 url,
1545 encryption,
1546 endpoint,
1547 storage_integration,
1548 credentials,
1549 })
1550}
1551
1552/// Parses options separated by blank spaces, commas, or new lines like:
1553/// ABORT_DETACHED_QUERY = { TRUE | FALSE }
1554/// [ ACTIVE_PYTHON_PROFILER = { 'LINE' | 'MEMORY' } ]
1555/// [ BINARY_INPUT_FORMAT = '\<string\>' ]
1556fn parse_session_options(
1557 parser: &mut Parser,
1558 set: bool,
1559) -> Result<Vec<KeyValueOption>, ParserError> {
1560 let mut options: Vec<KeyValueOption> = Vec::new();
1561 let empty = String::new;
1562 loop {
1563 let next_token = parser.peek_token();
1564 match next_token.token {
1565 Token::SemiColon | Token::EOF => break,
1566 Token::Comma => {
1567 parser.advance_token();
1568 continue;
1569 }
1570 Token::Word(key) => {
1571 parser.advance_token();
1572 if set {
1573 let option = parser.parse_key_value_option(&key)?;
1574 options.push(option);
1575 } else {
1576 options.push(KeyValueOption {
1577 option_name: key.value,
1578 option_value: KeyValueOptionKind::Single(Value::Placeholder(empty())),
1579 });
1580 }
1581 }
1582 _ => {
1583 return parser.expected("another option or end of statement", next_token);
1584 }
1585 }
1586 }
1587 if options.is_empty() {
1588 Err(ParserError::ParserError(
1589 "expected at least one option".to_string(),
1590 ))
1591 } else {
1592 Ok(options)
1593 }
1594}
1595
1596/// Parsing a property of identity or autoincrement column option
1597/// Syntax:
1598/// ```sql
1599/// [ (seed , increment) | START num INCREMENT num ] [ ORDER | NOORDER ]
1600/// ```
1601/// [Snowflake]: https://docs.snowflake.com/en/sql-reference/sql/create-table
1602fn parse_identity_property(parser: &mut Parser) -> Result<IdentityProperty, ParserError> {
1603 let parameters = if parser.consume_token(&Token::LParen) {
1604 let seed = parser.parse_number()?;
1605 parser.expect_token(&Token::Comma)?;
1606 let increment = parser.parse_number()?;
1607 parser.expect_token(&Token::RParen)?;
1608
1609 Some(IdentityPropertyFormatKind::FunctionCall(
1610 IdentityParameters { seed, increment },
1611 ))
1612 } else if parser.parse_keyword(Keyword::START) {
1613 let seed = parser.parse_number()?;
1614 parser.expect_keyword_is(Keyword::INCREMENT)?;
1615 let increment = parser.parse_number()?;
1616
1617 Some(IdentityPropertyFormatKind::StartAndIncrement(
1618 IdentityParameters { seed, increment },
1619 ))
1620 } else {
1621 None
1622 };
1623 let order = match parser.parse_one_of_keywords(&[Keyword::ORDER, Keyword::NOORDER]) {
1624 Some(Keyword::ORDER) => Some(IdentityPropertyOrder::Order),
1625 Some(Keyword::NOORDER) => Some(IdentityPropertyOrder::NoOrder),
1626 _ => None,
1627 };
1628 Ok(IdentityProperty { parameters, order })
1629}
1630
1631/// Parsing a policy property of column option
1632/// Syntax:
1633/// ```sql
1634/// <policy_name> [ USING ( <col_name> , <cond_col1> , ... )
1635/// ```
1636/// [Snowflake]: https://docs.snowflake.com/en/sql-reference/sql/create-table
1637fn parse_column_policy_property(
1638 parser: &mut Parser,
1639 with: bool,
1640) -> Result<ColumnPolicyProperty, ParserError> {
1641 let policy_name = parser.parse_object_name(false)?;
1642 let using_columns = if parser.parse_keyword(Keyword::USING) {
1643 parser.expect_token(&Token::LParen)?;
1644 let columns = parser.parse_comma_separated(|p| p.parse_identifier())?;
1645 parser.expect_token(&Token::RParen)?;
1646 Some(columns)
1647 } else {
1648 None
1649 };
1650
1651 Ok(ColumnPolicyProperty {
1652 with,
1653 policy_name,
1654 using_columns,
1655 })
1656}
1657
1658/// Parsing tags list of column
1659/// Syntax:
1660/// ```sql
1661/// ( <tag_name> = '<tag_value>' [ , <tag_name> = '<tag_value>' , ... ] )
1662/// ```
1663/// [Snowflake]: https://docs.snowflake.com/en/sql-reference/sql/create-table
1664fn parse_column_tags(parser: &mut Parser, with: bool) -> Result<TagsColumnOption, ParserError> {
1665 parser.expect_token(&Token::LParen)?;
1666 let tags = parser.parse_comma_separated(Parser::parse_tag)?;
1667 parser.expect_token(&Token::RParen)?;
1668
1669 Ok(TagsColumnOption { with, tags })
1670}
1671
1672/// Parse snowflake show objects.
1673/// <https://docs.snowflake.com/en/sql-reference/sql/show-objects>
1674fn parse_show_objects(terse: bool, parser: &mut Parser) -> Result<Statement, ParserError> {
1675 let show_options = parser.parse_show_stmt_options()?;
1676 Ok(Statement::ShowObjects(ShowObjects {
1677 terse,
1678 show_options,
1679 }))
1680}