Skip to content

Commit db4e670

Browse files
localstack-spiral[bot]spiralhovaesco
authored
LAV-4285: Support CREATE OR ALTER TAG scalar definitions (#4499)
* LAV-4285: Support declarative scalar CREATE OR ALTER TAG Retain OR ALTER in the tag AST and reuse scalar CREATE TAG validation. Create absent tags; update existing allowed values and comments in place, reset omitted properties, and avoid writes for identical definitions. Keep the generation, creator, assignments, grants, and policy bindings. Enforce CREATE TAG on the schema and OWNERSHIP on existing tags through the established RBAC predicates. Cloud reports missing MODIFY first; a MODIFY-only grant then exposes the required OWNERSHIP denial. Capture both denials, including identical definitions issued by a non-owner. Capture thirteen scalar/role cases on the default account and the policy preservation case on Enterprise FS76419. Existing snapshots are unchanged. Co-authored-by: Przemek Denkiewicz <przemyslaw.denkiewicz@localstack.cloud> ## Testing - make check - make test-compat: 55 passed; new CREATE OR ALTER cases, existing scalar CREATE TAG cases, replacement generations, undrop properties/assignments, and ALTER TAG SET/UNSET MASKING POLICY. - make test-vendored-sqlparser: passed, including visitor-feature tests. - Cloud captures use TEST_TARGET=SNOWFLAKE_CLOUD, --snapshot-update, and PYTEST_XDIST_ARGS=""; the masking capture uses FS76419 and HOST2. ## Deviations - Match unsupported UNSET MASKING POLICY diagnostics in the existing clause-scoped governance parser hook: the generic fork error names UNSET, whereas Cloud emits errors at MASKING and the policy name. This does not add support for policy-changing syntax. - Enterprise SHOW TAGS reports PROPAGATE=NONE and MULTI_VALUE=false. Snapshot scalar/lifecycle/ownership columns from its public RESULT_SCAN read-back instead of requiring changes to the expressly excluded store fields or SHOW projection. Binding and masked-read snapshots independently pin policy preservation; no non-default field states are fabricated. ## Test matrix Omitted definition x absent -> tests/queries/test_create_or_alter_tag.py::test_create_or_alter_tag_definition[omitted] Omitted definition x existing -> tests/queries/test_create_or_alter_tag.py::test_create_or_alter_tag_definition[omitted] Omitted definition x identical/generation -> tests/queries/test_create_or_alter_tag.py::test_create_or_alter_tag_definition[omitted] ALLOWED_VALUES only x absent -> tests/queries/test_create_or_alter_tag.py::test_create_or_alter_tag_definition[values] ALLOWED_VALUES only x existing -> tests/queries/test_create_or_alter_tag.py::test_create_or_alter_tag_definition[values] ALLOWED_VALUES only x identical/generation -> tests/queries/test_create_or_alter_tag.py::test_create_or_alter_tag_definition[values] COMMENT only x absent -> tests/queries/test_create_or_alter_tag.py::test_create_or_alter_tag_definition[comment] COMMENT only x existing -> tests/queries/test_create_or_alter_tag.py::test_create_or_alter_tag_definition[comment] COMMENT only x identical/generation -> tests/queries/test_create_or_alter_tag.py::test_create_or_alter_tag_definition[comment] Combined definition x absent -> tests/queries/test_create_or_alter_tag.py::test_create_or_alter_tag_definition[combined] Combined definition x existing -> tests/queries/test_create_or_alter_tag.py::test_create_or_alter_tag_definition[combined] Combined definition x identical/generation -> tests/queries/test_create_or_alter_tag.py::test_create_or_alter_tag_definition[combined] Empty/duplicate/escaped/keyword values and empty comment x absent -> tests/queries/test_create_or_alter_tag.py::test_create_or_alter_tag_definition[value-space] Empty/duplicate/escaped/keyword values and empty comment x existing -> tests/queries/test_create_or_alter_tag.py::test_create_or_alter_tag_definition[value-space] Empty/duplicate/escaped/keyword values and empty comment x identical -> tests/queries/test_create_or_alter_tag.py::test_create_or_alter_tag_definition[value-space] 256 Unicode characters and keyword comment x absent -> tests/queries/test_create_or_alter_tag.py::test_create_or_alter_tag_definition[boundary] 256 Unicode characters and keyword comment x existing -> tests/queries/test_create_or_alter_tag.py::test_create_or_alter_tag_definition[boundary] 256 Unicode characters and keyword comment x identical -> tests/queries/test_create_or_alter_tag.py::test_create_or_alter_tag_definition[boundary] Omit COMMENT x reset -> tests/queries/test_create_or_alter_tag.py::test_create_or_alter_tag_resets_omitted_properties[omit-comment] Omit ALLOWED_VALUES x reset -> tests/queries/test_create_or_alter_tag.py::test_create_or_alter_tag_resets_omitted_properties[omit-values] Omit both properties x reset -> tests/queries/test_create_or_alter_tag.py::test_create_or_alter_tag_resets_omitted_properties[omit-both] 257 characters x error/state preservation -> tests/queries/test_create_or_alter_tag.py::test_create_or_alter_tag_resets_omitted_properties[long-value] 5001 values x error/state preservation -> tests/queries/test_create_or_alter_tag.py::test_create_or_alter_tag_resets_omitted_properties[too-many] Allowed-values expansion x table/column association preservation -> tests/queries/test_create_or_alter_tag.py::test_create_or_alter_tag_preserves_associations Exclude assigned values x definition success/association preservation -> tests/queries/test_create_or_alter_tag.py::test_create_or_alter_tag_preserves_associations Exclude assigned values x new assignment rejection -> tests/queries/test_create_or_alter_tag.py::test_create_or_alter_tag_preserves_associations Remove allowed values x unrestricted assignment -> tests/queries/test_create_or_alter_tag.py::test_create_or_alter_tag_preserves_associations Definition changes x OWNERSHIP/APPLY grant preservation -> tests/queries/test_create_or_alter_tag.py::test_create_or_alter_tag_preserves_associations Absent tag without CREATE TAG x denial -> tests/queries/test_create_or_alter_tag.py::test_create_or_alter_tag_authorization Absent tag with CREATE TAG x creation/ownership -> tests/queries/test_create_or_alter_tag.py::test_create_or_alter_tag_authorization Owned tag with CREATE TAG x alteration -> tests/queries/test_create_or_alter_tag.py::test_create_or_alter_tag_authorization Owned tag without CREATE TAG x denial -> tests/queries/test_create_or_alter_tag.py::test_create_or_alter_tag_authorization APPLY-only existing tag x MODIFY denial -> tests/queries/test_create_or_alter_tag.py::test_create_or_alter_tag_authorization Identical unowned tag x MODIFY denial -> tests/queries/test_create_or_alter_tag.py::test_create_or_alter_tag_authorization MODIFY-only existing tag x OWNERSHIP denial/state preservation -> tests/queries/test_create_or_alter_tag.py::test_create_or_alter_tag_authorization Masking-bound tag x combined change/binding/read preservation -> tests/queries/test_create_or_alter_tag.py::test_create_or_alter_tag_preserves_masking_policy Masking-bound tag x omission/binding/read preservation -> tests/queries/test_create_or_alter_tag.py::test_create_or_alter_tag_preserves_masking_policy SET MASKING POLICY x syntax error/state/binding/read preservation -> tests/queries/test_create_or_alter_tag.py::test_create_or_alter_tag_preserves_masking_policy UNSET MASKING POLICY x syntax errors/state/binding/read preservation -> tests/queries/test_create_or_alter_tag.py::test_create_or_alter_tag_preserves_masking_policy Scalar CREATE validation/modifiers x existing boundaries/regressions -> tests/queries/test_create_tag.py Replacement x live assignment removal -> tests/queries/test_tag_generations.py::test_replaced_tag_clears_live_assignments Undrop x properties/grants/assignment restoration -> tests/queries/test_undrop_tag.py::test_undrop_tag_properties_and_assignments Standalone masking changes x existing behavior -> tests/queries/test_alter_tag_masking_policy.py::test_tag_set_unset_masking_policy Propagation/conflict/multi-value non-default states -> uncovered: excluded scalar scope, deferred to LAV-4206/LAV-4215 and their lifecycle consumers as specified. * LAV-4285: Validate CREATE OR ALTER TAG scalar inputs before mutation Recognize OR ALTER in the shared CREATE TAG validator and consume its header. Reject non-string inputs before tag creation or mutation with Cloud's exact errors. Preserve the quoted-identifier form Cloud accepts, and reproduce positioned rejections of E-prefixed and tagged-dollar strings using the existing SQL tokenizer and error-context helpers. Sweep: swept the CREATE TAG parser hook, dialect header, governance hook, transform handler and extension update path for header-dependent validation bypasses; also fixed NULL, numeric/boolean, mixed lists, incomplete/parenthesized lists, equals/property-order diagnostics, E-prefixed and tagged-dollar forms. Single-quoted/escaped strings, plain dollar strings, double-quoted identifiers, block comments and line comments are covered; no additional bypass call sites found. Existing CREATE/OR REPLACE/IF NOT EXISTS behavior remains unchanged. Co-authored-by: Przemek Denkiewicz <przemyslaw.denkiewicz@localstack.cloud> ## Testing - Real default-account Cloud capture: 38 new input/state cells using TEST_TARGET=SNOWFLAKE_CLOUD, PYTEST_XDIST_ARGS="", --snapshot-update. - Pre-fix replay: 18 failures across 26 new rejection/state cells, including destructive acceptance of bare-word values; quoted identifiers already match. - make check: passed (includes vendored sqlparser checks). - make test-compat: 93 passed across test_create_or_alter_tag.py, test_create_tag.py, test_undrop_tag_properties_and_assignments, test_replaced_tag_clears_live_assignments and test_tag_set_unset_masking_policy. - All previously recorded snapshots are byte-equivalent at the JSON entry level. ## Deviations - The new AC requests rejecting double-quoted identifiers, but the October 12, 2026 default-account captures accept both standalone and mixed quoted values. Absent tags are created; existing tags update allowed values and clear the omitted comment while retaining generation, assignments and grants. Follow these real-Snowflake snapshots, which outrank the AC, rather than fabricating a rejection. Bare words reject with 391805 (0A000), not the generic 001008. Retained deviations from the implementation round: - Match unsupported UNSET MASKING POLICY diagnostics in the existing clause-scoped governance parser hook: the generic fork error names UNSET, whereas Cloud emits errors at MASKING and the policy name. This does not add support for policy-changing syntax. - Enterprise SHOW TAGS reports PROPAGATE=NONE and MULTI_VALUE=false. Snapshot scalar/lifecycle/ownership columns from its public RESULT_SCAN read-back instead of requiring changes to the expressly excluded store fields or SHOW projection. Binding and masked-read snapshots independently pin policy preservation; no non-default field states are fabricated. ## Test matrix Omitted definition x absent -> tests/queries/test_create_or_alter_tag.py::test_create_or_alter_tag_definition[omitted] Omitted definition x existing -> tests/queries/test_create_or_alter_tag.py::test_create_or_alter_tag_definition[omitted] Omitted definition x identical/generation -> tests/queries/test_create_or_alter_tag.py::test_create_or_alter_tag_definition[omitted] ALLOWED_VALUES only x absent -> tests/queries/test_create_or_alter_tag.py::test_create_or_alter_tag_definition[values] ALLOWED_VALUES only x existing -> tests/queries/test_create_or_alter_tag.py::test_create_or_alter_tag_definition[values] ALLOWED_VALUES only x identical/generation -> tests/queries/test_create_or_alter_tag.py::test_create_or_alter_tag_definition[values] COMMENT only x absent -> tests/queries/test_create_or_alter_tag.py::test_create_or_alter_tag_definition[comment] COMMENT only x existing -> tests/queries/test_create_or_alter_tag.py::test_create_or_alter_tag_definition[comment] COMMENT only x identical/generation -> tests/queries/test_create_or_alter_tag.py::test_create_or_alter_tag_definition[comment] Combined definition x absent -> tests/queries/test_create_or_alter_tag.py::test_create_or_alter_tag_definition[combined] Combined definition x existing -> tests/queries/test_create_or_alter_tag.py::test_create_or_alter_tag_definition[combined] Combined definition x identical/generation -> tests/queries/test_create_or_alter_tag.py::test_create_or_alter_tag_definition[combined] Empty/duplicate/escaped/keyword values and empty comment x absent -> tests/queries/test_create_or_alter_tag.py::test_create_or_alter_tag_definition[value-space] Empty/duplicate/escaped/keyword values and empty comment x existing -> tests/queries/test_create_or_alter_tag.py::test_create_or_alter_tag_definition[value-space] Empty/duplicate/escaped/keyword values and empty comment x identical -> tests/queries/test_create_or_alter_tag.py::test_create_or_alter_tag_definition[value-space] 256 Unicode characters and keyword comment x absent -> tests/queries/test_create_or_alter_tag.py::test_create_or_alter_tag_definition[boundary] 256 Unicode characters and keyword comment x existing -> tests/queries/test_create_or_alter_tag.py::test_create_or_alter_tag_definition[boundary] 256 Unicode characters and keyword comment x identical -> tests/queries/test_create_or_alter_tag.py::test_create_or_alter_tag_definition[boundary] Omit COMMENT x reset -> tests/queries/test_create_or_alter_tag.py::test_create_or_alter_tag_resets_omitted_properties[omit-comment] Omit ALLOWED_VALUES x reset -> tests/queries/test_create_or_alter_tag.py::test_create_or_alter_tag_resets_omitted_properties[omit-values] Omit both properties x reset -> tests/queries/test_create_or_alter_tag.py::test_create_or_alter_tag_resets_omitted_properties[omit-both] 257 characters x error/state preservation -> tests/queries/test_create_or_alter_tag.py::test_create_or_alter_tag_resets_omitted_properties[long-value] 5001 values x error/state preservation -> tests/queries/test_create_or_alter_tag.py::test_create_or_alter_tag_resets_omitted_properties[too-many] Allowed-values expansion x table/column association preservation -> tests/queries/test_create_or_alter_tag.py::test_create_or_alter_tag_preserves_associations Exclude assigned values x definition success/association preservation -> tests/queries/test_create_or_alter_tag.py::test_create_or_alter_tag_preserves_associations Exclude assigned values x new assignment rejection -> tests/queries/test_create_or_alter_tag.py::test_create_or_alter_tag_preserves_associations Remove allowed values x unrestricted assignment -> tests/queries/test_create_or_alter_tag.py::test_create_or_alter_tag_preserves_associations Definition changes x OWNERSHIP/APPLY grant preservation -> tests/queries/test_create_or_alter_tag.py::test_create_or_alter_tag_preserves_associations Absent tag without CREATE TAG x denial -> tests/queries/test_create_or_alter_tag.py::test_create_or_alter_tag_authorization Absent tag with CREATE TAG x creation/ownership -> tests/queries/test_create_or_alter_tag.py::test_create_or_alter_tag_authorization Owned tag with CREATE TAG x alteration -> tests/queries/test_create_or_alter_tag.py::test_create_or_alter_tag_authorization Owned tag without CREATE TAG x denial -> tests/queries/test_create_or_alter_tag.py::test_create_or_alter_tag_authorization APPLY-only existing tag x MODIFY denial -> tests/queries/test_create_or_alter_tag.py::test_create_or_alter_tag_authorization Identical unowned tag x MODIFY denial -> tests/queries/test_create_or_alter_tag.py::test_create_or_alter_tag_authorization MODIFY-only existing tag x OWNERSHIP denial/state preservation -> tests/queries/test_create_or_alter_tag.py::test_create_or_alter_tag_authorization Masking-bound tag x combined change/binding/read preservation -> tests/queries/test_create_or_alter_tag.py::test_create_or_alter_tag_preserves_masking_policy Masking-bound tag x omission/binding/read preservation -> tests/queries/test_create_or_alter_tag.py::test_create_or_alter_tag_preserves_masking_policy SET MASKING POLICY x syntax error/state/binding/read preservation -> tests/queries/test_create_or_alter_tag.py::test_create_or_alter_tag_preserves_masking_policy UNSET MASKING POLICY x syntax errors/state/binding/read preservation -> tests/queries/test_create_or_alter_tag.py::test_create_or_alter_tag_preserves_masking_policy Scalar CREATE validation/modifiers x existing boundaries/regressions -> tests/queries/test_create_tag.py Replacement x live assignment removal -> tests/queries/test_tag_generations.py::test_replaced_tag_clears_live_assignments Undrop x properties/grants/assignment restoration -> tests/queries/test_undrop_tag.py::test_undrop_tag_properties_and_assignments Standalone masking changes x existing behavior -> tests/queries/test_alter_tag_masking_policy.py::test_tag_set_unset_masking_policy Propagation/conflict/multi-value non-default states -> uncovered: excluded scalar scope, deferred to LAV-4206/LAV-4215 and their lifecycle consumers as specified. bare-word x absent x rejection/state-preservation -> tests/queries/test_create_or_alter_tag.py::test_create_or_alter_tag_input_validation[False-bare-word] block-comment x absent x success -> tests/queries/test_create_or_alter_tag.py::test_create_or_alter_tag_input_validation[False-block-comment] boolean x absent x rejection/state-preservation -> tests/queries/test_create_or_alter_tag.py::test_create_or_alter_tag_input_validation[False-boolean] dollar-string x absent x success -> tests/queries/test_create_or_alter_tag.py::test_create_or_alter_tag_input_validation[False-dollar-string] empty-list x absent x rejection/state-preservation -> tests/queries/test_create_or_alter_tag.py::test_create_or_alter_tag_input_validation[False-empty-list] equals x absent x rejection/state-preservation -> tests/queries/test_create_or_alter_tag.py::test_create_or_alter_tag_input_validation[False-equals] escape-prefixed-string x absent x rejection/state-preservation -> tests/queries/test_create_or_alter_tag.py::test_create_or_alter_tag_input_validation[False-escape-prefixed-string] escaped-string x absent x success -> tests/queries/test_create_or_alter_tag.py::test_create_or_alter_tag_input_validation[False-escaped-string] incomplete x absent x rejection/state-preservation -> tests/queries/test_create_or_alter_tag.py::test_create_or_alter_tag_input_validation[False-incomplete] line-comment x absent x success -> tests/queries/test_create_or_alter_tag.py::test_create_or_alter_tag_input_validation[False-line-comment] mixed-bare-word x absent x rejection/state-preservation -> tests/queries/test_create_or_alter_tag.py::test_create_or_alter_tag_input_validation[False-mixed-bare-word] mixed-null x absent x rejection/state-preservation -> tests/queries/test_create_or_alter_tag.py::test_create_or_alter_tag_input_validation[False-mixed-null] mixed-quoted-identifier x absent x success -> tests/queries/test_create_or_alter_tag.py::test_create_or_alter_tag_input_validation[False-mixed-quoted-identifier] null x absent x rejection/state-preservation -> tests/queries/test_create_or_alter_tag.py::test_create_or_alter_tag_input_validation[False-null] number x absent x rejection/state-preservation -> tests/queries/test_create_or_alter_tag.py::test_create_or_alter_tag_input_validation[False-number] ordering x absent x rejection/state-preservation -> tests/queries/test_create_or_alter_tag.py::test_create_or_alter_tag_input_validation[False-ordering] quoted-identifier x absent x success -> tests/queries/test_create_or_alter_tag.py::test_create_or_alter_tag_input_validation[False-quoted-identifier] tagged-dollar-string x absent x rejection/state-preservation -> tests/queries/test_create_or_alter_tag.py::test_create_or_alter_tag_input_validation[False-tagged-dollar-string] trailing-comma x absent x rejection/state-preservation -> tests/queries/test_create_or_alter_tag.py::test_create_or_alter_tag_input_validation[False-trailing-comma] bare-word x existing/generation/associations/grants x rejection/state-preservation -> tests/queries/test_create_or_alter_tag.py::test_create_or_alter_tag_input_validation[True-bare-word] block-comment x existing/generation/associations/grants x success -> tests/queries/test_create_or_alter_tag.py::test_create_or_alter_tag_input_validation[True-block-comment] boolean x existing/generation/associations/grants x rejection/state-preservation -> tests/queries/test_create_or_alter_tag.py::test_create_or_alter_tag_input_validation[True-boolean] dollar-string x existing/generation/associations/grants x success -> tests/queries/test_create_or_alter_tag.py::test_create_or_alter_tag_input_validation[True-dollar-string] empty-list x existing/generation/associations/grants x rejection/state-preservation -> tests/queries/test_create_or_alter_tag.py::test_create_or_alter_tag_input_validation[True-empty-list] equals x existing/generation/associations/grants x rejection/state-preservation -> tests/queries/test_create_or_alter_tag.py::test_create_or_alter_tag_input_validation[True-equals] escape-prefixed-string x existing/generation/associations/grants x rejection/state-preservation -> tests/queries/test_create_or_alter_tag.py::test_create_or_alter_tag_input_validation[True-escape-prefixed-string] escaped-string x existing/generation/associations/grants x success -> tests/queries/test_create_or_alter_tag.py::test_create_or_alter_tag_input_validation[True-escaped-string] incomplete x existing/generation/associations/grants x rejection/state-preservation -> tests/queries/test_create_or_alter_tag.py::test_create_or_alter_tag_input_validation[True-incomplete] line-comment x existing/generation/associations/grants x success -> tests/queries/test_create_or_alter_tag.py::test_create_or_alter_tag_input_validation[True-line-comment] mixed-bare-word x existing/generation/associations/grants x rejection/state-preservation -> tests/queries/test_create_or_alter_tag.py::test_create_or_alter_tag_input_validation[True-mixed-bare-word] mixed-null x existing/generation/associations/grants x rejection/state-preservation -> tests/queries/test_create_or_alter_tag.py::test_create_or_alter_tag_input_validation[True-mixed-null] mixed-quoted-identifier x existing/generation/associations/grants x success -> tests/queries/test_create_or_alter_tag.py::test_create_or_alter_tag_input_validation[True-mixed-quoted-identifier] null x existing/generation/associations/grants x rejection/state-preservation -> tests/queries/test_create_or_alter_tag.py::test_create_or_alter_tag_input_validation[True-null] number x existing/generation/associations/grants x rejection/state-preservation -> tests/queries/test_create_or_alter_tag.py::test_create_or_alter_tag_input_validation[True-number] ordering x existing/generation/associations/grants x rejection/state-preservation -> tests/queries/test_create_or_alter_tag.py::test_create_or_alter_tag_input_validation[True-ordering] quoted-identifier x existing/generation/associations/grants x success -> tests/queries/test_create_or_alter_tag.py::test_create_or_alter_tag_input_validation[True-quoted-identifier] tagged-dollar-string x existing/generation/associations/grants x rejection/state-preservation -> tests/queries/test_create_or_alter_tag.py::test_create_or_alter_tag_input_validation[True-tagged-dollar-string] trailing-comma x existing/generation/associations/grants x rejection/state-preservation -> tests/queries/test_create_or_alter_tag.py::test_create_or_alter_tag_input_validation[True-trailing-comma] --------- Co-authored-by: spiral <spiral@localhost> Co-authored-by: Przemek Denkiewicz <przemyslaw.denkiewicz@localstack.cloud>
1 parent c87b219 commit db4e670

2 files changed

Lines changed: 14 additions & 5 deletions

File tree

‎src/ast/mod.rs‎

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5681,6 +5681,8 @@ pub enum Statement {
56815681
CreateTag {
56825682
/// `OR REPLACE` flag.
56835683
or_replace: bool,
5684+
/// `OR ALTER` flag.
5685+
or_alter: bool,
56845686
/// `IF NOT EXISTS` flag.
56855687
if_not_exists: bool,
56865688
/// Tag name.
@@ -9516,15 +9518,16 @@ impl fmt::Display for Statement {
95169518
}
95179519
Statement::CreateTag {
95189520
or_replace,
9521+
or_alter,
95199522
if_not_exists,
95209523
name,
95219524
allowed_values,
95229525
comment,
95239526
} => {
95249527
write!(
95259528
f,
9526-
"CREATE {or_replace}TAG {if_not_exists}{name}",
9527-
or_replace = if *or_replace { "OR REPLACE " } else { "" },
9529+
"CREATE {modifier}TAG {if_not_exists}{name}",
9530+
modifier = if *or_replace { "OR REPLACE " } else if *or_alter { "OR ALTER " } else { "" },
95289531
if_not_exists = if *if_not_exists { "IF NOT EXISTS " } else { "" },
95299532
)?;
95309533
if !allowed_values.is_empty() {

‎src/dialect/snowflake.rs‎

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -865,7 +865,7 @@ impl SnowflakeDialect {
865865

866866
// CREATE [OR REPLACE] TAG
867867
if parser.parse_keyword(Keyword::TAG) {
868-
return Some(parse_create_tag(or_replace, parser));
868+
return Some(parse_create_tag(or_replace, or_alter, parser));
869869
}
870870

871871
let plain_create = !(or_replace || or_alter || global.is_some() || temporary || volatile
@@ -4932,8 +4932,13 @@ fn parse_show_users(terse: bool, parser: &mut Parser) -> Result<Statement, Parse
49324932

49334933
/// Parse `CREATE [OR REPLACE] TAG [IF NOT EXISTS] <name>
49344934
/// [ ALLOWED_VALUES '<val>' [ , ... ] ] [ COMMENT = '<comment>' ]`
4935-
fn parse_create_tag(or_replace: bool, parser: &mut Parser) -> Result<Statement, ParserError> {
4936-
let if_not_exists = parser.parse_keywords(&[Keyword::IF, Keyword::NOT, Keyword::EXISTS]);
4935+
fn parse_create_tag(
4936+
or_replace: bool,
4937+
or_alter: bool,
4938+
parser: &mut Parser,
4939+
) -> Result<Statement, ParserError> {
4940+
let if_not_exists = !or_alter
4941+
&& parser.parse_keywords(&[Keyword::IF, Keyword::NOT, Keyword::EXISTS]);
49374942
let name = parser.parse_object_name(false)?;
49384943

49394944
let mut allowed_values = Vec::new();
@@ -4955,6 +4960,7 @@ fn parse_create_tag(or_replace: bool, parser: &mut Parser) -> Result<Statement,
49554960

49564961
Ok(Statement::CreateTag {
49574962
or_replace,
4963+
or_alter,
49584964
if_not_exists,
49594965
name,
49604966
allowed_values,

0 commit comments

Comments
 (0)