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