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