Skip to main content

sqlparser/dialect/
snowflake.rs

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