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