This is an automated email from the ASF dual-hosted git repository.

szehon-ho pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/spark.git


The following commit(s) were added to refs/heads/master by this push:
     new e3b91d89ed24 [SPARK-56249][CONNECT][SDP] Create AUTO CDC syntax for 
SCD Type 1 in SQL
e3b91d89ed24 is described below

commit e3b91d89ed2404c447deb60e6f050e8586d1df84
Author: andreas-neumann_data <[email protected]>
AuthorDate: Tue Jul 7 12:14:16 2026 -0700

    [SPARK-56249][CONNECT][SDP] Create AUTO CDC syntax for SCD Type 1 in SQL
    
    ### What changes were proposed in this pull request?
    Add SQL syntax for AUTO CDC (Change Data Capture) with SCD Type 1 
semantics. Two forms are supported:
    
    1. CREATE FLOW <name> AS AUTO CDC INTO <target> FROM <source> KEYS (...) 
SEQUENCE BY <expr>
    2. CREATE [OR REFRESH] STREAMING TABLE <name> FLOW AUTO CDC FROM <source> 
KEYS (...) SEQUENCE BY <expr>
    
    Optional clauses: APPLY AS DELETE WHEN, COLUMNS, COLUMNS * EXCEPT.
    
    ### Why are the changes needed?
    So far, there is only a Python API for Auto CDC; this adds a SQL API.
    
    ### Does this PR introduce _any_ user-facing change?
    Yes, it adds a new SQL syntax
    
    ### How was this patch tested?
    - New tests are added: AutoCdcParserSuite
    - some tests are failing in CI. They are all unrelated to my changes and 
also broken in master, except for
      [Run / Build modules: pyspark-connect-old-client]. It fails because it 
tests a client built on 4.0 to query the
      server built from the current commit. It lists the first 20 sql keywords, 
and expects a 13-char wide output because
      previously, AUTHORIZATION was among those 20 keywords. My addition of 
APPLY pushes AUTHORIZATION out
      of the first 20, and the longest keyword is now ASENSITIVE with 10 
characters. This test is quite brittle and has
      been fixed on master by using `.columns` instead of `.show`, but because 
the client is built from 4.0, this fix needs
      to be back-ported to 4.0, I cannot fix that in the current PR.
    
    ### Was this patch authored or co-authored using generative AI tooling?
    Generated-by: Opus 4.6
    
    Closes #56419 from anew/scd1-sql-api.
    
    Authored-by: andreas-neumann_data <[email protected]>
    Signed-off-by: Szehon Ho <[email protected]>
---
 docs/sql-ref-ansi-compliance.md                    |   4 +
 .../spark/sql/catalyst/parser/SqlBaseLexer.g4      |   4 +
 .../spark/sql/catalyst/parser/SqlBaseParser.g4     |  42 +-
 .../spark/sql/catalyst/parser/AstBuilder.scala     |  61 ++
 .../plans/logical/AutoCdcIntoCommand.scala         |  67 ++
 .../sql/catalyst/plans/logical/v2Commands.scala    |  50 ++
 .../jdbc/SparkConnectDatabaseMetaDataSuite.scala   |   2 +-
 .../spark/sql/execution/SparkSqlParser.scala       |  74 +-
 .../sql-tests/results/keywords-enforced.sql.out    |   4 +
 .../resources/sql-tests/results/keywords.sql.out   |   4 +
 .../sql-tests/results/nonansi/keywords.sql.out     |   4 +
 .../execution/command/v2/AutoCdcParserSuite.scala  | 844 +++++++++++++++++++++
 .../ThriftServerWithSparkContextSuite.scala        |   2 +-
 13 files changed, 1139 insertions(+), 23 deletions(-)

diff --git a/docs/sql-ref-ansi-compliance.md b/docs/sql-ref-ansi-compliance.md
index d28f0af5dd0a..c3eec6cc5ce3 100644
--- a/docs/sql-ref-ansi-compliance.md
+++ b/docs/sql-ref-ansi-compliance.md
@@ -423,6 +423,7 @@ Below is a list of all the keywords in Spark SQL.
 |ANTI|non-reserved|strict-non-reserved|non-reserved|
 |ANY|reserved|non-reserved|reserved|
 |ANY_VALUE|non-reserved|non-reserved|non-reserved|
+|APPLY|non-reserved|non-reserved|non-reserved|
 |APPROX|non-reserved|non-reserved|non-reserved|
 |ARCHIVE|non-reserved|non-reserved|non-reserved|
 |ARRAY|non-reserved|non-reserved|reserved|
@@ -432,6 +433,7 @@ Below is a list of all the keywords in Spark SQL.
 |AT|non-reserved|non-reserved|reserved|
 |ATOMIC|non-reserved|non-reserved|non-reserved|
 |AUTHORIZATION|reserved|non-reserved|reserved|
+|AUTO|non-reserved|non-reserved|non-reserved|
 |BEGIN|non-reserved|non-reserved|non-reserved|
 |BERNOULLI|non-reserved|non-reserved|non-reserved|
 |BETWEEN|non-reserved|non-reserved|reserved|
@@ -456,6 +458,7 @@ Below is a list of all the keywords in Spark SQL.
 |CAST|reserved|non-reserved|reserved|
 |CATALOG|non-reserved|non-reserved|non-reserved|
 |CATALOGS|non-reserved|non-reserved|non-reserved|
+|CDC|non-reserved|non-reserved|non-reserved|
 |CHANGE|non-reserved|non-reserved|non-reserved|
 |CHANGES|non-reserved|non-reserved|non-reserved|
 |CHAR|non-reserved|non-reserved|reserved|
@@ -744,6 +747,7 @@ Below is a list of all the keywords in Spark SQL.
 |SELECT|reserved|non-reserved|reserved|
 |SEMI|non-reserved|strict-non-reserved|non-reserved|
 |SEPARATED|non-reserved|non-reserved|non-reserved|
+|SEQUENCE|non-reserved|non-reserved|non-reserved|
 |SERDE|non-reserved|non-reserved|non-reserved|
 |SERDEPROPERTIES|non-reserved|non-reserved|non-reserved|
 |SESSION_USER|reserved|non-reserved|reserved|
diff --git 
a/sql/api/src/main/antlr4/org/apache/spark/sql/catalyst/parser/SqlBaseLexer.g4 
b/sql/api/src/main/antlr4/org/apache/spark/sql/catalyst/parser/SqlBaseLexer.g4
index f4a9aa1d5471..e15be3fb76ec 100644
--- 
a/sql/api/src/main/antlr4/org/apache/spark/sql/catalyst/parser/SqlBaseLexer.g4
+++ 
b/sql/api/src/main/antlr4/org/apache/spark/sql/catalyst/parser/SqlBaseLexer.g4
@@ -140,6 +140,7 @@ AND: 'AND';
 ANTI: 'ANTI';
 ANY: 'ANY';
 ANY_VALUE: 'ANY_VALUE';
+APPLY: 'APPLY';
 APPROX: 'APPROX';
 ARCHIVE: 'ARCHIVE';
 ARRAY: 'ARRAY' {incComplexTypeLevelCounter();};
@@ -149,6 +150,7 @@ ASENSITIVE: 'ASENSITIVE';
 AT: 'AT';
 ATOMIC: 'ATOMIC';
 AUTHORIZATION: 'AUTHORIZATION';
+AUTO: 'AUTO';
 BEGIN: 'BEGIN';
 BERNOULLI: 'BERNOULLI';
 BETWEEN: 'BETWEEN';
@@ -173,6 +175,7 @@ CASE: 'CASE';
 CAST: 'CAST';
 CATALOG: 'CATALOG';
 CATALOGS: 'CATALOGS';
+CDC: 'CDC';
 CHANGE: 'CHANGE';
 CHANGES: 'CHANGES';
 CHAR: 'CHAR';
@@ -459,6 +462,7 @@ SECURITY: 'SECURITY';
 SELECT: 'SELECT';
 SEMI: 'SEMI';
 SEPARATED: 'SEPARATED';
+SEQUENCE: 'SEQUENCE';
 SERDE: 'SERDE';
 SERDEPROPERTIES: 'SERDEPROPERTIES';
 SESSION_USER: 'SESSION_USER';
diff --git 
a/sql/api/src/main/antlr4/org/apache/spark/sql/catalyst/parser/SqlBaseParser.g4 
b/sql/api/src/main/antlr4/org/apache/spark/sql/catalyst/parser/SqlBaseParser.g4
index 7d75aaebd546..bf10656d6e77 100644
--- 
a/sql/api/src/main/antlr4/org/apache/spark/sql/catalyst/parser/SqlBaseParser.g4
+++ 
b/sql/api/src/main/antlr4/org/apache/spark/sql/catalyst/parser/SqlBaseParser.g4
@@ -420,8 +420,9 @@ statement
     | unsupportedHiveNativeCommands .*?                                
#failNativeCommand
     | createPipelineDatasetHeader (LEFT_PAREN tableElementList? RIGHT_PAREN)? 
tableProvider?
         createTableClauses
-        (AS query)?                                                    
#createPipelineDataset
+        (AS query | FLOW autoCdcBody)?                                 
#createPipelineDataset
     | createPipelineFlowHeader insertInto query                        
#createPipelineInsertIntoFlow
+    | createPipelineFlowHeader autoCdcCommand                          
#createFlowAutoCdc
     ;
 
 materializedView
@@ -754,6 +755,37 @@ dmlStatementNoWith
         notMatchedBySourceClause*                                              
    #mergeIntoTable
     ;
 
+autoCdcCommand
+    : AUTO CDC INTO target=multipartIdentifier
+        autoCdcParameters
+    ;
+
+autoCdcBody
+    : AUTO CDC autoCdcParameters
+    ;
+
+autoCdcParameters
+    : FROM source=relationPrimary
+        KEYS LEFT_PAREN keys=identifierSeq RIGHT_PAREN
+        autoCdcDeleteClause?
+        autoCdcSequenceByClause
+        autoCdcColumnsClause?
+    ;
+
+autoCdcDeleteClause
+    : APPLY AS DELETE WHEN deleteCondition=booleanExpression
+    ;
+
+autoCdcSequenceByClause
+    : SEQUENCE BY sequence=expression
+    ;
+
+autoCdcColumnsClause
+    : COLUMNS (
+        LEFT_PAREN columns=identifierSeq RIGHT_PAREN |
+        ASTERISK EXCEPT LEFT_PAREN exceptCols=identifierSeq RIGHT_PAREN)
+    ;
+
 identifierReference
     : IDENTIFIER_KW LEFT_PAREN expression RIGHT_PAREN
     | multipartIdentifier
@@ -1973,6 +2005,7 @@ ansiNonReserved
     | ANALYZE
     | ANTI
     | ANY_VALUE
+    | APPLY
     | APPROX
     | ARCHIVE
     | ARRAY
@@ -1980,6 +2013,7 @@ ansiNonReserved
     | ASENSITIVE
     | AT
     | ATOMIC
+    | AUTO
     | BEGIN
     | BERNOULLI
     | BETWEEN
@@ -2001,6 +2035,7 @@ ansiNonReserved
     | CASCADE
     | CATALOG
     | CATALOGS
+    | CDC
     | CHANGE
     | CHANGES
     | CHAR
@@ -2232,6 +2267,7 @@ ansiNonReserved
     | SECURITY
     | SEMI
     | SEPARATED
+    | SEQUENCE
     | SERDE
     | SERDEPROPERTIES
     | SET
@@ -2360,6 +2396,7 @@ nonReserved
     | AND
     | ANY
     | ANY_VALUE
+    | APPLY
     | APPROX
     | ARCHIVE
     | ARRAY
@@ -2369,6 +2406,7 @@ nonReserved
     | AT
     | ATOMIC
     | AUTHORIZATION
+    | AUTO
     | BEGIN
     | BERNOULLI
     | BETWEEN
@@ -2394,6 +2432,7 @@ nonReserved
     | CAST
     | CATALOG
     | CATALOGS
+    | CDC
     | CHANGE
     | CHANGES
     | CHAR
@@ -2669,6 +2708,7 @@ nonReserved
     | SECURITY
     | SELECT
     | SEPARATED
+    | SEQUENCE
     | SERDE
     | SERDEPROPERTIES
     | SESSION_USER
diff --git 
a/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/parser/AstBuilder.scala
 
b/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/parser/AstBuilder.scala
index 519b62c4cecd..5832b14f72bc 100644
--- 
a/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/parser/AstBuilder.scala
+++ 
b/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/parser/AstBuilder.scala
@@ -1367,6 +1367,56 @@ class AstBuilder extends DataTypeAstBuilder
       withSchemaEvolution)
   }
 
+  protected def buildAutoCdcIntoCommand(ctx: AutoCdcCommandContext): 
AutoCdcIntoCommand =
+    withOrigin(ctx) {
+      val target = UnresolvedIdentifier(visitMultipartIdentifier(ctx.target))
+      val params = parseAutoCdcParams(ctx.autoCdcParameters())
+      AutoCdcIntoCommand(
+        targetTable = target,
+        source = params.source,
+        keys = params.keys,
+        deleteCondition = params.deleteCondition,
+        sequenceByExpr = params.sequencing,
+        includeColumns = params.includeColumns,
+        excludeColumns = params.excludeColumns)
+    }
+
+  protected def parseAutoCdcParams(params: AutoCdcParametersContext): 
AutoCdcParams =
+    withOrigin(params) {
+      // The source is parsed as a general `relationPrimary` to allow richer 
streaming queries
+      // (e.g. streaming TVFs, or a STREAM(...) source with options and 
watermark). It must still
+      // read from a streaming source; a batch relation is not a valid AUTO 
CDC source.
+      val source = plan(params.source)
+      if (!source.isStreaming) {
+        withOrigin(params.source) {
+          operationNotAllowed(
+            "AUTO CDC source must be a streaming query, e.g. 
STREAM(<table>).", params.source)
+        }
+      }
+      val keys = 
visitIdentifierSeq(params.keys).map(UnresolvedAttribute.quoted)
+      val deleteCondition = Option(params.autoCdcDeleteClause())
+        .map(c => expression(c.deleteCondition))
+      val sequencing = expression(params.autoCdcSequenceByClause().sequence)
+
+      val columnsClause = Option(params.autoCdcColumnsClause())
+      val includeColumns = columnsClause.collect {
+        case c if c.columns != null =>
+          visitIdentifierSeq(c.columns).map(UnresolvedAttribute.quoted)
+      }
+      val excludeColumns = columnsClause.collect {
+        case c if c.exceptCols != null =>
+          visitIdentifierSeq(c.exceptCols).map(UnresolvedAttribute.quoted)
+      }
+
+      AutoCdcParams(
+        source = source,
+        keys = keys,
+        deleteCondition = deleteCondition,
+        sequencing = sequencing,
+        includeColumns = includeColumns,
+        excludeColumns = excludeColumns)
+    }
+
   /**
    * Returns the parameters for [[UnresolvedExecuteImmediate]] logical plan.
    * Expected format:
@@ -7758,3 +7808,14 @@ class AstBuilder extends DataTypeAstBuilder
     }
   }
 }
+
+/**
+ * Parameters parsed from an AUTO CDC clause.
+ */
+case class AutoCdcParams(
+    source: LogicalPlan,
+    keys: Seq[UnresolvedAttribute],
+    deleteCondition: Option[Expression],
+    sequencing: Expression,
+    includeColumns: Option[Seq[UnresolvedAttribute]],
+    excludeColumns: Option[Seq[UnresolvedAttribute]])
diff --git 
a/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/plans/logical/AutoCdcIntoCommand.scala
 
b/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/plans/logical/AutoCdcIntoCommand.scala
new file mode 100644
index 000000000000..eacdf51927a6
--- /dev/null
+++ 
b/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/plans/logical/AutoCdcIntoCommand.scala
@@ -0,0 +1,67 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.spark.sql.catalyst.plans.logical
+
+import org.apache.spark.sql.catalyst.analysis.UnresolvedAttribute
+import org.apache.spark.sql.catalyst.expressions.Expression
+
+/**
+ * Logical plan node for an AUTO CDC INTO command, used by Spark Declarative 
Pipelines.
+ *
+ * This represents a CDC (Change Data Capture) operation that applies an 
ordered change event
+ * stream from [[source]] into [[targetTable]] using SCD Type 1 (upsert) 
semantics.
+ *
+ * This node serves as a parse-time placeholder for a pipeline CDC definition 
and cannot be
+ * executed directly. It will be interpreted by the pipeline submodule once 
execution support
+ * is added (SPARK-57402). The [[targetTable]] and [[source]] relations are 
exposed as the node's
+ * children (left and right respectively) so the analyzer resolves them 
through the normal plan
+ * resolution path.
+ *
+ * @param targetTable    The target table to apply changes into, as an 
`UnresolvedIdentifier`.
+ *                       Exposed as the node's left child.
+ * @param source         The source relation providing the change events, 
parsed from a general
+ *                       `relationPrimary` (typically a STREAM(...) source 
marked as a streaming
+ *                       read). Exposed as the node's right child.
+ * @param keys           Column(s) that uniquely identify a row in the target 
table.
+ * @param deleteCondition An optional expression that marks a source row as a 
DELETE operation.
+ *                        When absent, all source rows are treated as upserts.
+ * @param sequenceByExpr Expression that orders CDC events to correctly 
resolve out-of-order
+ *                       arrivals. Must evaluate to a sortable type. Required.
+ * @param includeColumns An explicit list of source columns to include in the 
target table.
+ *                       [[None]] when no COLUMNS clause was specified. 
Mutually exclusive with
+ *                       [[excludeColumns]].
+ * @param excludeColumns Source columns to exclude from the target table 
(i.e., all columns
+ *                       except these). [[None]] when no COLUMNS clause was 
specified. Mutually
+ *                       exclusive with [[includeColumns]].
+ */
+case class AutoCdcIntoCommand(
+    targetTable: LogicalPlan,
+    source: LogicalPlan,
+    keys: Seq[UnresolvedAttribute],
+    deleteCondition: Option[Expression],
+    sequenceByExpr: Expression,
+    includeColumns: Option[Seq[UnresolvedAttribute]],
+    excludeColumns: Option[Seq[UnresolvedAttribute]]
+) extends BinaryCommand {
+  override def left: LogicalPlan = targetTable
+  override def right: LogicalPlan = source
+
+  override protected def withNewChildrenInternal(
+      newLeft: LogicalPlan, newRight: LogicalPlan): AutoCdcIntoCommand =
+    copy(targetTable = newLeft, source = newRight)
+}
diff --git 
a/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/plans/logical/v2Commands.scala
 
b/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/plans/logical/v2Commands.scala
index 40cf5009b97d..eed6fd7ca6ca 100644
--- 
a/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/plans/logical/v2Commands.scala
+++ 
b/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/plans/logical/v2Commands.scala
@@ -824,6 +824,56 @@ case class CreateStreamingTable(
     copy(name = newChild)
 }
 
+/**
+ * Command parsed from `CREATE STREAMING TABLE <name> FLOW AUTO CDC ...` SQL 
syntax.
+ * This command serves as a parse-time placeholder for a pipeline CDC 
definition and cannot be
+ * executed directly. It will be interpreted by the pipeline submodule once 
execution support
+ * is added (SPARK-57402).
+ *
+ * The target of the CDC operation is the streaming table itself (given by 
[[name]]).
+ *
+ * [[name]] and [[source]] are exposed as the node's children (left and right 
respectively) so the
+ * analyzer resolves them through the normal plan resolution path, mirroring 
how
+ * [[CreatePipelineDatasetAsSelect]] exposes its name and query.
+ *
+ * @param name           The streaming table name, which also serves as the 
CDC target. Exposed as
+ *                       the node's left child.
+ * @param columns        User-specified columns for the streaming table.
+ * @param partitioning   Column-based partitioning for the streaming table.
+ * @param tableSpec      Additional table specs.
+ * @param ifNotExists    Whether the table should only be created if it 
doesn't already exist.
+ * @param source         The source relation providing the change events, 
parsed from a general
+ *                       `relationPrimary` (typically a STREAM(...) source 
marked as a streaming
+ *                       read). Exposed as the node's right child.
+ * @param keys           Column(s) that uniquely identify a row in the target 
table.
+ * @param deleteCondition An optional expression that marks a source row as a 
DELETE operation.
+ * @param sequenceByExpr Expression that orders CDC events to resolve 
out-of-order arrivals.
+ * @param includeColumns An explicit list of source columns to include. 
[[None]] when no COLUMNS
+ *                       clause was specified. Mutually exclusive with 
[[excludeColumns]].
+ * @param excludeColumns Source columns to exclude. [[None]] when no COLUMNS 
clause was specified.
+ *                       Mutually exclusive with [[includeColumns]].
+ */
+case class CreateStreamingTableAutoCdc(
+    name: LogicalPlan,
+    columns: Seq[ColumnDefinition],
+    partitioning: Seq[Transform],
+    tableSpec: TableSpecBase,
+    ifNotExists: Boolean,
+    source: LogicalPlan,
+    keys: Seq[UnresolvedAttribute],
+    deleteCondition: Option[Expression],
+    sequenceByExpr: Expression,
+    includeColumns: Option[Seq[UnresolvedAttribute]],
+    excludeColumns: Option[Seq[UnresolvedAttribute]]
+) extends BinaryCommand with CreatePipelineDataset {
+  override def left: LogicalPlan = name
+  override def right: LogicalPlan = source
+
+  override protected def withNewChildrenInternal(
+      newLeft: LogicalPlan, newRight: LogicalPlan): 
CreateStreamingTableAutoCdc =
+    copy(name = newLeft, source = newRight)
+}
+
 /**
  * Replace a table with a v2 catalog.
  *
diff --git 
a/sql/connect/client/jdbc/src/test/scala/org/apache/spark/sql/connect/client/jdbc/SparkConnectDatabaseMetaDataSuite.scala
 
b/sql/connect/client/jdbc/src/test/scala/org/apache/spark/sql/connect/client/jdbc/SparkConnectDatabaseMetaDataSuite.scala
index 4c07c4d1354c..6c35f73f5742 100644
--- 
a/sql/connect/client/jdbc/src/test/scala/org/apache/spark/sql/connect/client/jdbc/SparkConnectDatabaseMetaDataSuite.scala
+++ 
b/sql/connect/client/jdbc/src/test/scala/org/apache/spark/sql/connect/client/jdbc/SparkConnectDatabaseMetaDataSuite.scala
@@ -210,7 +210,7 @@ class SparkConnectDatabaseMetaDataSuite extends 
ConnectFunSuite with RemoteSpark
       val metadata = conn.getMetaData
       // scalastyle:off line.size.limit
       // CURRENT_PATH and SYSTEM are excluded: getSQLKeywords drops SQL:2003 
reserved words (see companion).
-      assert(metadata.getSQLKeywords === 
"ADD,AFTER,AGGREGATE,ALIGN,ALWAYS,ANALYZE,ANTI,ANY_VALUE,APPROX,ARCHIVE,ASC,BERNOULLI,BIN,BINDING,BIN_DISTRIBUTE_RATIO,BIN_END,BIN_START,BUCKET,BUCKETS,BYTE,CACHE,CASCADE,CATALOG,CATALOGS,CHANGE,CHANGES,CLEAR,CLUSTER,CLUSTERED,CODEGEN,COLLATION,COLLATIONS,COLLECTION,COLUMNS,COMMENT,COMPACT,COMPACTIONS,COMPENSATION,COMPUTE,CONCATENATE,CONTAINS,CONTINUE,COST,CURRENT_DATABASE,CURRENT_SCHEMA,DATA,DATABASE,DATABASES,DATEADD,DATEDIFF,DATE_ADD,DATE_DIFF,
 [...]
+      assert(metadata.getSQLKeywords === 
"ADD,AFTER,AGGREGATE,ALIGN,ALWAYS,ANALYZE,ANTI,ANY_VALUE,APPLY,APPROX,ARCHIVE,ASC,AUTO,BERNOULLI,BIN,BINDING,BIN_DISTRIBUTE_RATIO,BIN_END,BIN_START,BUCKET,BUCKETS,BYTE,CACHE,CASCADE,CATALOG,CATALOGS,CDC,CHANGE,CHANGES,CLEAR,CLUSTER,CLUSTERED,CODEGEN,COLLATION,COLLATIONS,COLLECTION,COLUMNS,COMMENT,COMPACT,COMPACTIONS,COMPENSATION,COMPUTE,CONCATENATE,CONTAINS,CONTINUE,COST,CURRENT_DATABASE,CURRENT_SCHEMA,DATA,DATABASE,DATABASES,DATEADD,DATEDIFF,DATE
 [...]
       // scalastyle:on line.size.limit
     }
   }
diff --git 
a/sql/core/src/main/scala/org/apache/spark/sql/execution/SparkSqlParser.scala 
b/sql/core/src/main/scala/org/apache/spark/sql/execution/SparkSqlParser.scala
index 8ff2161a2965..2eb2c7e91441 100644
--- 
a/sql/core/src/main/scala/org/apache/spark/sql/execution/SparkSqlParser.scala
+++ 
b/sql/core/src/main/scala/org/apache/spark/sql/execution/SparkSqlParser.scala
@@ -1571,6 +1571,19 @@ class SparkSqlAstBuilder extends AstBuilder {
     )
   }
 
+  override def visitCreateFlowAutoCdc(
+      ctx: CreateFlowAutoCdcContext): LogicalPlan = withOrigin(ctx) {
+    val flowHeaderCtx = ctx.createPipelineFlowHeader()
+    val ident = withIdentClause(flowHeaderCtx.flowName, 
UnresolvedIdentifier(_))
+    val commentOpt = Option(flowHeaderCtx.commentSpec()).map(visitCommentSpec)
+    val autoCdcInto = buildAutoCdcIntoCommand(ctx.autoCdcCommand())
+    CreateFlowCommand(
+      name = ident,
+      flowOperation = autoCdcInto,
+      comment = commentOpt
+    )
+  }
+
   override def visitCreatePipelineDataset(
       ctx: CreatePipelineDatasetContext): LogicalPlan = withOrigin(ctx) {
     val createPipelineDatasetHeaderCtx = ctx.createPipelineDatasetHeader()
@@ -1603,7 +1616,7 @@ class SparkSqlAstBuilder extends AstBuilder {
       partitionExpressions(partTransforms, partCols, ctx) ++
         clusterBySpec.map(_.asTransform)
 
-    // Because the createTableClauses grammar is reused for 
createPipelineDataset but pipeline
+    // Because the createTableClauses grammar is reused for pipeline datasets 
but pipeline
     // datasets don't support bucketing, options, storage location, or Hive 
SerDe, validate they
     // are not set.
     if (bucketSpec.isDefined) {
@@ -1646,6 +1659,10 @@ class SparkSqlAstBuilder extends AstBuilder {
     )
 
     if (createPipelineDatasetHeaderCtx.materializedView() != null) {
+      if (ctx.autoCdcBody() != null) {
+        throw operationNotAllowed(
+          "AUTO CDC is only supported for STREAMING TABLE, not MATERIALIZED 
VIEW.", ctx)
+      }
       val query: ParserRuleContext = Option(ctx.query).getOrElse(
         throw operationNotAllowed(
           s"Unable to find query for CREATE $syntaxTypeErrorStr statement.", 
ctx)
@@ -1660,25 +1677,42 @@ class SparkSqlAstBuilder extends AstBuilder {
         ifNotExists = ifNotExists
       )
     } else if (createPipelineDatasetHeaderCtx.streamingTable() != null) {
-      Option(ctx.query) match {
-        case Some(query) =>
-          CreateStreamingTableAsSelect(
-            name = datasetIdentifier,
-            columns = colDefs,
-            partitioning = partitioning,
-            tableSpec = spec,
-            query = plan(query),
-            originalText = source(query),
-            ifNotExists = ifNotExists
-          )
-        case None =>
-          CreateStreamingTable(
-            name = datasetIdentifier,
-            columns = colDefs,
-            partitioning = partitioning,
-            tableSpec = spec,
-            ifNotExists = ifNotExists
-          )
+      if (ctx.autoCdcBody() != null) {
+        val params = parseAutoCdcParams(ctx.autoCdcBody().autoCdcParameters())
+        CreateStreamingTableAutoCdc(
+          name = datasetIdentifier,
+          columns = colDefs,
+          partitioning = partitioning,
+          tableSpec = spec,
+          ifNotExists = ifNotExists,
+          source = params.source,
+          keys = params.keys,
+          deleteCondition = params.deleteCondition,
+          sequenceByExpr = params.sequencing,
+          includeColumns = params.includeColumns,
+          excludeColumns = params.excludeColumns
+        )
+      } else {
+        Option(ctx.query) match {
+          case Some(query) =>
+            CreateStreamingTableAsSelect(
+              name = datasetIdentifier,
+              columns = colDefs,
+              partitioning = partitioning,
+              tableSpec = spec,
+              query = plan(query),
+              originalText = source(query),
+              ifNotExists = ifNotExists
+            )
+          case None =>
+            CreateStreamingTable(
+              name = datasetIdentifier,
+              columns = colDefs,
+              partitioning = partitioning,
+              tableSpec = spec,
+              ifNotExists = ifNotExists
+            )
+        }
       }
     } else {
       // Should never be possible based on grammar definition.
diff --git 
a/sql/core/src/test/resources/sql-tests/results/keywords-enforced.sql.out 
b/sql/core/src/test/resources/sql-tests/results/keywords-enforced.sql.out
index cbee1375aba8..0c57178048eb 100644
--- a/sql/core/src/test/resources/sql-tests/results/keywords-enforced.sql.out
+++ b/sql/core/src/test/resources/sql-tests/results/keywords-enforced.sql.out
@@ -16,6 +16,7 @@ AND   true
 ANTI   false
 ANY    true
 ANY_VALUE      false
+APPLY  false
 APPROX false
 ARCHIVE        false
 ARRAY  false
@@ -25,6 +26,7 @@ ASENSITIVE    false
 AT     false
 ATOMIC false
 AUTHORIZATION  true
+AUTO   false
 BEGIN  false
 BERNOULLI      false
 BETWEEN        false
@@ -49,6 +51,7 @@ CASE  true
 CAST   true
 CATALOG        false
 CATALOGS       false
+CDC    false
 CHANGE false
 CHANGES        false
 CHAR   false
@@ -335,6 +338,7 @@ SECURITY    false
 SELECT true
 SEMI   false
 SEPARATED      false
+SEQUENCE       false
 SERDE  false
 SERDEPROPERTIES        false
 SESSION_USER   true
diff --git a/sql/core/src/test/resources/sql-tests/results/keywords.sql.out 
b/sql/core/src/test/resources/sql-tests/results/keywords.sql.out
index dfdbc5fccb56..e0528a75c6ef 100644
--- a/sql/core/src/test/resources/sql-tests/results/keywords.sql.out
+++ b/sql/core/src/test/resources/sql-tests/results/keywords.sql.out
@@ -16,6 +16,7 @@ AND   false
 ANTI   false
 ANY    false
 ANY_VALUE      false
+APPLY  false
 APPROX false
 ARCHIVE        false
 ARRAY  false
@@ -25,6 +26,7 @@ ASENSITIVE    false
 AT     false
 ATOMIC false
 AUTHORIZATION  false
+AUTO   false
 BEGIN  false
 BERNOULLI      false
 BETWEEN        false
@@ -49,6 +51,7 @@ CASE  false
 CAST   false
 CATALOG        false
 CATALOGS       false
+CDC    false
 CHANGE false
 CHANGES        false
 CHAR   false
@@ -335,6 +338,7 @@ SECURITY    false
 SELECT false
 SEMI   false
 SEPARATED      false
+SEQUENCE       false
 SERDE  false
 SERDEPROPERTIES        false
 SESSION_USER   false
diff --git 
a/sql/core/src/test/resources/sql-tests/results/nonansi/keywords.sql.out 
b/sql/core/src/test/resources/sql-tests/results/nonansi/keywords.sql.out
index dfdbc5fccb56..e0528a75c6ef 100644
--- a/sql/core/src/test/resources/sql-tests/results/nonansi/keywords.sql.out
+++ b/sql/core/src/test/resources/sql-tests/results/nonansi/keywords.sql.out
@@ -16,6 +16,7 @@ AND   false
 ANTI   false
 ANY    false
 ANY_VALUE      false
+APPLY  false
 APPROX false
 ARCHIVE        false
 ARRAY  false
@@ -25,6 +26,7 @@ ASENSITIVE    false
 AT     false
 ATOMIC false
 AUTHORIZATION  false
+AUTO   false
 BEGIN  false
 BERNOULLI      false
 BETWEEN        false
@@ -49,6 +51,7 @@ CASE  false
 CAST   false
 CATALOG        false
 CATALOGS       false
+CDC    false
 CHANGE false
 CHANGES        false
 CHAR   false
@@ -335,6 +338,7 @@ SECURITY    false
 SELECT false
 SEMI   false
 SEPARATED      false
+SEQUENCE       false
 SERDE  false
 SERDEPROPERTIES        false
 SESSION_USER   false
diff --git 
a/sql/core/src/test/scala/org/apache/spark/sql/execution/command/v2/AutoCdcParserSuite.scala
 
b/sql/core/src/test/scala/org/apache/spark/sql/execution/command/v2/AutoCdcParserSuite.scala
new file mode 100644
index 000000000000..975ea7fb1559
--- /dev/null
+++ 
b/sql/core/src/test/scala/org/apache/spark/sql/execution/command/v2/AutoCdcParserSuite.scala
@@ -0,0 +1,844 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.spark.sql.execution.command.v2
+
+import org.apache.spark.sql.catalyst.analysis.{
+  AnalysisTest, NamedStreamingRelation, UnresolvedAttribute, 
UnresolvedIdentifier,
+  UnresolvedRelation}
+import org.apache.spark.sql.catalyst.parser.ParseException
+import org.apache.spark.sql.catalyst.plans.logical.{
+  AutoCdcIntoCommand,
+  CreateFlowCommand,
+  CreateStreamingTableAutoCdc,
+  LogicalPlan,
+  SubqueryAlias
+}
+import org.apache.spark.sql.connector.expressions.{ClusterByTransform, 
FieldReference, IdentityTransform}
+import org.apache.spark.sql.execution.SparkSqlParser
+import org.apache.spark.sql.types.IntegerType
+
+/**
+ * Parser tests for AUTO CDC syntax.
+ *
+ * Covers two supported forms:
+ *   1. CREATE FLOW <name> [COMMENT ...] AS AUTO CDC INTO <target> ...
+ *   2. CREATE STREAMING TABLE <name> FLOW AUTO CDC ...
+ *
+ * Snapshot CDC, SCD Type 2, IGNORE NULL UPDATES, and APPLY AS TRUNCATE WHEN 
are not
+ * supported and should fail to parse. The standalone AUTO CDC INTO form 
(without CREATE FLOW
+ * or CREATE STREAMING TABLE) is also not supported.
+ */
+class AutoCdcParserSuite extends CommandSuiteBase with AnalysisTest {
+  protected lazy val parser = new SparkSqlParser()
+
+  /**
+   * Unwrap a `STREAM(...)` AUTO CDC source. Streaming sources are parsed 
through the general
+   * `relationPrimary` rule, which wraps the streaming read in a 
[[NamedStreamingRelation]].
+   */
+  private def streamSource(source: LogicalPlan): UnresolvedRelation = {
+    val named = source.asInstanceOf[NamedStreamingRelation]
+    assert(named.isStreaming)
+    named.child.asInstanceOf[UnresolvedRelation]
+  }
+
+  // 
---------------------------------------------------------------------------
+  // CREATE FLOW ... AS AUTO CDC INTO
+  // 
---------------------------------------------------------------------------
+
+  test("CREATE FLOW AS AUTO CDC INTO - minimal form") {
+    val plan = parser.parsePlan(
+      """CREATE FLOW myflow AS AUTO CDC INTO target
+        |FROM STREAM(source)
+        |KEYS (key1, key2)
+        |SEQUENCE BY timestamp""".stripMargin)
+
+    val cmd = plan.asInstanceOf[CreateFlowCommand]
+    assert(cmd.name.asInstanceOf[UnresolvedIdentifier].nameParts == 
Seq("myflow"))
+    assert(cmd.comment.isEmpty)
+
+    val cdc = cmd.flowOperation.asInstanceOf[AutoCdcIntoCommand]
+    assert(cdc.targetTable.asInstanceOf[UnresolvedIdentifier].nameParts == 
Seq("target"))
+    val source = streamSource(cdc.source)
+    assert(source.multipartIdentifier == Seq("source"))
+    assert(cdc.keys.map(_.name) == Seq("key1", "key2"))
+    assert(cdc.deleteCondition.isEmpty)
+    assert(cdc.sequenceByExpr == UnresolvedAttribute("timestamp"))
+    assert(cdc.includeColumns.isEmpty)
+    assert(cdc.excludeColumns.isEmpty)
+  }
+
+  test("CREATE FLOW AS AUTO CDC INTO - multipart source name") {
+    val plan = parser.parsePlan(
+      """CREATE FLOW myflow AS AUTO CDC INTO target
+        |FROM STREAM(mycat.myschema.source)
+        |KEYS (id)
+        |SEQUENCE BY ts""".stripMargin)
+
+    val cdc = 
plan.asInstanceOf[CreateFlowCommand].flowOperation.asInstanceOf[AutoCdcIntoCommand]
+    val source = streamSource(cdc.source)
+    assert(source.multipartIdentifier == Seq("mycat", "myschema", "source"))
+  }
+
+  test("CREATE FLOW AS AUTO CDC INTO - with COMMENT") {
+    val plan = parser.parsePlan(
+      """CREATE FLOW myflow COMMENT 'my comment' AS AUTO CDC INTO target
+        |FROM STREAM(source)
+        |KEYS (id)
+        |SEQUENCE BY ts""".stripMargin)
+
+    val cmd = plan.asInstanceOf[CreateFlowCommand]
+    assert(cmd.comment == Some("my comment"))
+  }
+
+  test("CREATE FLOW AS AUTO CDC INTO - multipart flow name") {
+    val plan = parser.parsePlan(
+      """CREATE FLOW mycat.myschema.myflow AS AUTO CDC INTO target
+        |FROM STREAM(source)
+        |KEYS (id)
+        |SEQUENCE BY ts""".stripMargin)
+
+    val cmd = plan.asInstanceOf[CreateFlowCommand]
+    assert(cmd.name.asInstanceOf[UnresolvedIdentifier].nameParts ==
+      Seq("mycat", "myschema", "myflow"))
+  }
+
+  test("CREATE FLOW AS AUTO CDC INTO - two-part target table name") {
+    val plan = parser.parsePlan(
+      """CREATE FLOW f AS AUTO CDC INTO myschema.mytable
+        |FROM STREAM(source)
+        |KEYS (k)
+        |SEQUENCE BY ts""".stripMargin)
+
+    val cdc = 
plan.asInstanceOf[CreateFlowCommand].flowOperation.asInstanceOf[AutoCdcIntoCommand]
+    assert(cdc.targetTable.asInstanceOf[UnresolvedIdentifier].nameParts ==
+      Seq("myschema", "mytable"))
+  }
+
+  test("CREATE FLOW AS AUTO CDC INTO - three-part target table name") {
+    val plan = parser.parsePlan(
+      """CREATE FLOW f AS AUTO CDC INTO mycat.myschema.mytable
+        |FROM STREAM(source)
+        |KEYS (k)
+        |SEQUENCE BY ts""".stripMargin)
+
+    val cdc = 
plan.asInstanceOf[CreateFlowCommand].flowOperation.asInstanceOf[AutoCdcIntoCommand]
+    assert(cdc.targetTable.asInstanceOf[UnresolvedIdentifier].nameParts ==
+      Seq("mycat", "myschema", "mytable"))
+  }
+
+  test("CREATE FLOW AS AUTO CDC INTO - APPLY AS DELETE WHEN") {
+    val plan = parser.parsePlan(
+      """CREATE FLOW f AS AUTO CDC INTO target
+        |FROM STREAM(source)
+        |KEYS (id)
+        |APPLY AS DELETE WHEN op = 'DELETE'
+        |SEQUENCE BY ts""".stripMargin)
+
+    val cdc = 
plan.asInstanceOf[CreateFlowCommand].flowOperation.asInstanceOf[AutoCdcIntoCommand]
+    assert(cdc.deleteCondition.isDefined)
+    assert(cdc.deleteCondition.get.sql.contains("op"))
+  }
+
+  test("CREATE FLOW AS AUTO CDC INTO - COLUMNS include list") {
+    val plan = parser.parsePlan(
+      """CREATE FLOW f AS AUTO CDC INTO target
+        |FROM STREAM(source)
+        |KEYS (id)
+        |SEQUENCE BY ts
+        |COLUMNS (id, name, value)""".stripMargin)
+
+    val cdc = 
plan.asInstanceOf[CreateFlowCommand].flowOperation.asInstanceOf[AutoCdcIntoCommand]
+    assert(cdc.includeColumns.get.map(_.name) == Seq("id", "name", "value"))
+    assert(cdc.excludeColumns.isEmpty)
+  }
+
+  test("CREATE FLOW AS AUTO CDC INTO - COLUMNS * EXCEPT list") {
+    val plan = parser.parsePlan(
+      """CREATE FLOW f AS AUTO CDC INTO target
+        |FROM STREAM(source)
+        |KEYS (id)
+        |SEQUENCE BY ts
+        |COLUMNS * EXCEPT (op, ts)""".stripMargin)
+
+    val cdc = 
plan.asInstanceOf[CreateFlowCommand].flowOperation.asInstanceOf[AutoCdcIntoCommand]
+    assert(cdc.includeColumns.isEmpty)
+    assert(cdc.excludeColumns.get.map(_.name) == Seq("op", "ts"))
+  }
+
+  test("CREATE FLOW AS AUTO CDC INTO - all clauses combined") {
+    val plan = parser.parsePlan(
+      """CREATE FLOW f AS AUTO CDC INTO target
+        |FROM STREAM(source)
+        |KEYS (key1, key2)
+        |APPLY AS DELETE WHEN key3 = 3
+        |SEQUENCE BY timestamp
+        |COLUMNS (key1, key2, key3, timestamp)""".stripMargin)
+
+    val cdc = 
plan.asInstanceOf[CreateFlowCommand].flowOperation.asInstanceOf[AutoCdcIntoCommand]
+    assert(cdc.keys.map(_.name) == Seq("key1", "key2"))
+    assert(cdc.deleteCondition.isDefined)
+    assert(cdc.sequenceByExpr == UnresolvedAttribute("timestamp"))
+    assert(cdc.includeColumns.get.map(_.name) == Seq("key1", "key2", "key3", 
"timestamp"))
+  }
+
+  // 
---------------------------------------------------------------------------
+  // CREATE STREAMING TABLE ... FLOW AUTO CDC
+  // 
---------------------------------------------------------------------------
+
+  test("CREATE STREAMING TABLE FLOW AUTO CDC - minimal form") {
+    val plan = parser.parsePlan(
+      """CREATE STREAMING TABLE target
+        |FLOW AUTO CDC
+        |FROM STREAM(source)
+        |KEYS (key1, key2)
+        |SEQUENCE BY timestamp""".stripMargin)
+
+    val cmd = plan.asInstanceOf[CreateStreamingTableAutoCdc]
+    assert(cmd.name.asInstanceOf[UnresolvedIdentifier].nameParts == 
Seq("target"))
+    assert(!cmd.ifNotExists)
+    val source = streamSource(cmd.source)
+    assert(source.multipartIdentifier == Seq("source"))
+    assert(cmd.keys.map(_.name) == Seq("key1", "key2"))
+    assert(cmd.deleteCondition.isEmpty)
+    assert(cmd.sequenceByExpr == UnresolvedAttribute("timestamp"))
+    assert(cmd.includeColumns.isEmpty)
+    assert(cmd.excludeColumns.isEmpty)
+  }
+
+  test("CREATE STREAMING TABLE FLOW AUTO CDC - multipart source name") {
+    val plan = parser.parsePlan(
+      """CREATE STREAMING TABLE target
+        |FLOW AUTO CDC
+        |FROM STREAM(mycat.myschema.source)
+        |KEYS (id)
+        |SEQUENCE BY ts""".stripMargin)
+
+    val cmd = plan.asInstanceOf[CreateStreamingTableAutoCdc]
+    val source = streamSource(cmd.source)
+    assert(source.multipartIdentifier == Seq("mycat", "myschema", "source"))
+  }
+
+  test("CREATE STREAMING TABLE IF NOT EXISTS FLOW AUTO CDC") {
+    val plan = parser.parsePlan(
+      """CREATE STREAMING TABLE IF NOT EXISTS target
+        |FLOW AUTO CDC
+        |FROM STREAM(source)
+        |KEYS (id)
+        |SEQUENCE BY ts""".stripMargin)
+
+    val cmd = plan.asInstanceOf[CreateStreamingTableAutoCdc]
+    assert(cmd.ifNotExists)
+  }
+
+  test("CREATE STREAMING TABLE FLOW AUTO CDC - multipart table name") {
+    val plan = parser.parsePlan(
+      """CREATE STREAMING TABLE myschema.mytable
+        |FLOW AUTO CDC
+        |FROM STREAM(source)
+        |KEYS (id)
+        |SEQUENCE BY ts""".stripMargin)
+
+    val cmd = plan.asInstanceOf[CreateStreamingTableAutoCdc]
+    assert(cmd.name.asInstanceOf[UnresolvedIdentifier].nameParts == 
Seq("myschema", "mytable"))
+  }
+
+  test("CREATE STREAMING TABLE FLOW AUTO CDC - APPLY AS DELETE WHEN") {
+    val plan = parser.parsePlan(
+      """CREATE STREAMING TABLE target
+        |FLOW AUTO CDC
+        |FROM STREAM(source)
+        |KEYS (id)
+        |APPLY AS DELETE WHEN op = 'DELETE'
+        |SEQUENCE BY ts""".stripMargin)
+
+    val cmd = plan.asInstanceOf[CreateStreamingTableAutoCdc]
+    assert(cmd.deleteCondition.isDefined)
+    assert(cmd.deleteCondition.get.sql.contains("op"))
+  }
+
+  test("CREATE STREAMING TABLE FLOW AUTO CDC - COLUMNS include list") {
+    val plan = parser.parsePlan(
+      """CREATE STREAMING TABLE target
+        |FLOW AUTO CDC
+        |FROM STREAM(source)
+        |KEYS (id)
+        |SEQUENCE BY ts
+        |COLUMNS (id, name, value)""".stripMargin)
+
+    val cmd = plan.asInstanceOf[CreateStreamingTableAutoCdc]
+    assert(cmd.includeColumns.get.map(_.name) == Seq("id", "name", "value"))
+    assert(cmd.excludeColumns.isEmpty)
+  }
+
+  test("CREATE STREAMING TABLE FLOW AUTO CDC - COLUMNS * EXCEPT list") {
+    val plan = parser.parsePlan(
+      """CREATE STREAMING TABLE target
+        |FLOW AUTO CDC
+        |FROM STREAM(source)
+        |KEYS (id)
+        |SEQUENCE BY ts
+        |COLUMNS * EXCEPT (op, ts)""".stripMargin)
+
+    val cmd = plan.asInstanceOf[CreateStreamingTableAutoCdc]
+    assert(cmd.includeColumns.isEmpty)
+    assert(cmd.excludeColumns.get.map(_.name) == Seq("op", "ts"))
+  }
+
+  test("CREATE STREAMING TABLE FLOW AUTO CDC - all clauses combined") {
+    val plan = parser.parsePlan(
+      """CREATE STREAMING TABLE target
+        |FLOW AUTO CDC
+        |FROM STREAM(source)
+        |KEYS (key1, key2)
+        |APPLY AS DELETE WHEN key3 = 3
+        |SEQUENCE BY timestamp
+        |COLUMNS * EXCEPT (key4)""".stripMargin)
+
+    val cmd = plan.asInstanceOf[CreateStreamingTableAutoCdc]
+    assert(cmd.keys.map(_.name) == Seq("key1", "key2"))
+    assert(cmd.deleteCondition.isDefined)
+    assert(cmd.sequenceByExpr == UnresolvedAttribute("timestamp"))
+    assert(cmd.excludeColumns.get.map(_.name) == Seq("key4"))
+  }
+
+  test("CREATE STREAMING TABLE FLOW AUTO CDC - PARTITIONED BY is honored") {
+    val plan = parser.parsePlan(
+      """CREATE STREAMING TABLE target
+        |PARTITIONED BY (key1)
+        |FLOW AUTO CDC
+        |FROM STREAM(source)
+        |KEYS (key1)
+        |SEQUENCE BY ts""".stripMargin)
+
+    val cmd = plan.asInstanceOf[CreateStreamingTableAutoCdc]
+    assert(cmd.partitioning == 
Seq(IdentityTransform(FieldReference(Seq("key1")))))
+  }
+
+  test("CREATE STREAMING TABLE FLOW AUTO CDC - COMMENT is honored") {
+    val plan = parser.parsePlan(
+      """CREATE STREAMING TABLE target
+        |COMMENT 'my streaming table'
+        |FLOW AUTO CDC
+        |FROM STREAM(source)
+        |KEYS (id)
+        |SEQUENCE BY ts""".stripMargin)
+
+    val cmd = plan.asInstanceOf[CreateStreamingTableAutoCdc]
+    assert(cmd.tableSpec.comment == Some("my streaming table"))
+  }
+
+  test("CREATE STREAMING TABLE FLOW AUTO CDC - TBLPROPERTIES are honored") {
+    val plan = parser.parsePlan(
+      """CREATE STREAMING TABLE target
+        |TBLPROPERTIES ('key' = 'value', 'num' = '1')
+        |FLOW AUTO CDC
+        |FROM STREAM(source)
+        |KEYS (id)
+        |SEQUENCE BY ts""".stripMargin)
+
+    val cmd = plan.asInstanceOf[CreateStreamingTableAutoCdc]
+    assert(cmd.tableSpec.properties == Map("key" -> "value", "num" -> "1"))
+  }
+
+  test("CREATE STREAMING TABLE FLOW AUTO CDC - PARTITIONED BY, COMMENT, 
TBLPROPERTIES combined") {
+    val plan = parser.parsePlan(
+      """CREATE STREAMING TABLE target
+        |PARTITIONED BY (key1)
+        |COMMENT 'my streaming table'
+        |TBLPROPERTIES ('key' = 'value')
+        |FLOW AUTO CDC
+        |FROM STREAM(source)
+        |KEYS (key1)
+        |SEQUENCE BY ts""".stripMargin)
+
+    val cmd = plan.asInstanceOf[CreateStreamingTableAutoCdc]
+    assert(cmd.partitioning == 
Seq(IdentityTransform(FieldReference(Seq("key1")))))
+    assert(cmd.tableSpec.comment == Some("my streaming table"))
+    assert(cmd.tableSpec.properties == Map("key" -> "value"))
+  }
+
+  test("CREATE STREAMING TABLE FLOW AUTO CDC - CLUSTER BY is honored") {
+    val plan = parser.parsePlan(
+      """CREATE STREAMING TABLE target
+        |CLUSTER BY (key1)
+        |FLOW AUTO CDC
+        |FROM STREAM(source)
+        |KEYS (key1)
+        |SEQUENCE BY ts""".stripMargin)
+
+    val cmd = plan.asInstanceOf[CreateStreamingTableAutoCdc]
+    assert(cmd.partitioning == 
Seq(ClusterByTransform(Seq(FieldReference(Seq("key1"))))))
+  }
+
+  test("CREATE STREAMING TABLE FLOW AUTO CDC - USING provider is honored") {
+    val plan = parser.parsePlan(
+      """CREATE STREAMING TABLE target
+        |USING parquet
+        |FLOW AUTO CDC
+        |FROM STREAM(source)
+        |KEYS (id)
+        |SEQUENCE BY ts""".stripMargin)
+
+    val cmd = plan.asInstanceOf[CreateStreamingTableAutoCdc]
+    assert(cmd.tableSpec.provider == Some("parquet"))
+  }
+
+  test("CREATE STREAMING TABLE FLOW AUTO CDC - DEFAULT COLLATION is honored") {
+    val plan = parser.parsePlan(
+      """CREATE STREAMING TABLE target
+        |DEFAULT COLLATION UTF8_LCASE
+        |FLOW AUTO CDC
+        |FROM STREAM(source)
+        |KEYS (id)
+        |SEQUENCE BY ts""".stripMargin)
+
+    val cmd = plan.asInstanceOf[CreateStreamingTableAutoCdc]
+    assert(cmd.tableSpec.collation == Some("UTF8_LCASE"))
+  }
+
+  test("CREATE STREAMING TABLE FLOW AUTO CDC - column list is honored") {
+    val plan = parser.parsePlan(
+      """CREATE STREAMING TABLE target (id INT, name STRING)
+        |FLOW AUTO CDC
+        |FROM STREAM(source)
+        |KEYS (id)
+        |SEQUENCE BY ts""".stripMargin)
+
+    val cmd = plan.asInstanceOf[CreateStreamingTableAutoCdc]
+    assert(cmd.columns.map(_.name) == Seq("id", "name"))
+    assert(cmd.columns.head.dataType == IntegerType)
+  }
+
+  // 
---------------------------------------------------------------------------
+  // Error cases: missing required clause
+  // 
---------------------------------------------------------------------------
+
+  test("CREATE FLOW AS AUTO CDC INTO - SEQUENCE BY is required") {
+    checkError(
+      intercept[ParseException] {
+        parser.parsePlan(
+          """CREATE FLOW f AS AUTO CDC INTO target
+            |FROM STREAM(source)
+            |KEYS (id)""".stripMargin)
+      },
+      condition = "PARSE_SYNTAX_ERROR",
+      sqlState = "42601",
+      parameters = Map("error" -> "end of input", "hint" -> "")
+    )
+  }
+
+  test("CREATE STREAMING TABLE FLOW AUTO CDC - SEQUENCE BY is required") {
+    checkError(
+      intercept[ParseException] {
+        parser.parsePlan(
+          """CREATE STREAMING TABLE target
+            |FLOW AUTO CDC
+            |FROM STREAM(source)
+            |KEYS (id)""".stripMargin)
+      },
+      condition = "PARSE_SYNTAX_ERROR",
+      sqlState = "42601",
+      parameters = Map("error" -> "end of input", "hint" -> "")
+    )
+  }
+
+  // 
---------------------------------------------------------------------------
+  // Error cases: wrong clause order
+  // 
---------------------------------------------------------------------------
+
+  test("SEQUENCE BY before APPLY AS DELETE is not allowed") {
+    checkError(
+      intercept[ParseException] {
+        parser.parsePlan(
+          """CREATE FLOW f AS AUTO CDC INTO target
+            |FROM STREAM(source)
+            |KEYS (id)
+            |SEQUENCE BY ts
+            |APPLY AS DELETE WHEN a = 1""".stripMargin)
+      },
+      condition = "PARSE_SYNTAX_ERROR",
+      sqlState = "42601",
+      parameters = Map("error" -> "'APPLY'", "hint" -> "")
+    )
+  }
+
+  test("COLUMNS before SEQUENCE BY is not allowed") {
+    checkError(
+      intercept[ParseException] {
+        parser.parsePlan(
+          """CREATE FLOW f AS AUTO CDC INTO target
+            |FROM STREAM(source)
+            |KEYS (id)
+            |COLUMNS a, b
+            |SEQUENCE BY ts""".stripMargin)
+      },
+      condition = "PARSE_SYNTAX_ERROR",
+      sqlState = "42601",
+      parameters = Map("error" -> "'COLUMNS'", "hint" -> "")
+    )
+  }
+
+  // 
---------------------------------------------------------------------------
+  // Error cases: standalone form not supported
+  // 
---------------------------------------------------------------------------
+
+  test("standalone AUTO CDC INTO is not supported") {
+    checkError(
+      intercept[ParseException] {
+        parser.parsePlan(
+          """AUTO CDC INTO target
+            |FROM STREAM(source)
+            |KEYS (id)
+            |SEQUENCE BY ts""".stripMargin)
+      },
+      condition = "PARSE_SYNTAX_ERROR",
+      sqlState = "42601",
+      parameters = Map("error" -> "'AUTO'", "hint" -> "")
+    )
+  }
+
+  // 
---------------------------------------------------------------------------
+  // Source accepts richer streaming queries via relationPrimary
+  // 
---------------------------------------------------------------------------
+
+  test("CREATE FLOW AS AUTO CDC INTO - STREAM without parentheses is 
accepted") {
+    val plan = parser.parsePlan(
+      """CREATE FLOW f AS AUTO CDC INTO target
+        |FROM STREAM source
+        |KEYS (id)
+        |SEQUENCE BY ts""".stripMargin)
+
+    val cdc = 
plan.asInstanceOf[CreateFlowCommand].flowOperation.asInstanceOf[AutoCdcIntoCommand]
+    val source = streamSource(cdc.source)
+    assert(source.multipartIdentifier == Seq("source"))
+  }
+
+  test("CREATE FLOW AS AUTO CDC INTO - STREAM source with alias is accepted") {
+    val plan = parser.parsePlan(
+      """CREATE FLOW f AS AUTO CDC INTO target
+        |FROM STREAM(source) AS s
+        |KEYS (id)
+        |SEQUENCE BY ts""".stripMargin)
+
+    val cdc = 
plan.asInstanceOf[CreateFlowCommand].flowOperation.asInstanceOf[AutoCdcIntoCommand]
+    assert(cdc.source.isStreaming)
+    val alias = cdc.source.asInstanceOf[SubqueryAlias]
+    assert(alias.alias == "s")
+  }
+
+  test("CREATE FLOW AS AUTO CDC INTO - subquery over a STREAM is accepted") {
+    val plan = parser.parsePlan(
+      """CREATE FLOW f AS AUTO CDC INTO target
+        |FROM (SELECT * FROM STREAM changes WHERE operation != 'pre_image')
+        |KEYS (id)
+        |SEQUENCE BY ts""".stripMargin)
+
+    // The subquery wraps the STREAM read in Project/Filter/SubqueryAlias 
nodes; isStreaming
+    // propagates up through them, so the whole source is recognized as 
streaming.
+    val cdc = 
plan.asInstanceOf[CreateFlowCommand].flowOperation.asInstanceOf[AutoCdcIntoCommand]
+    assert(cdc.source.isStreaming)
+    assert(cdc.source.isInstanceOf[SubqueryAlias])
+  }
+
+  // 
---------------------------------------------------------------------------
+  // Error cases: source is a relationPrimary, not a full relation
+  // 
---------------------------------------------------------------------------
+
+  test("CREATE FLOW AS AUTO CDC INTO - a join source is not allowed") {
+    // The source is a `relationPrimary`, not a full `relation`, so trailing 
relation extensions
+    // such as a JOIN are not part of the source and fail to parse (a KEYS 
clause is expected).
+    checkError(
+      intercept[ParseException] {
+        parser.parsePlan(
+          """CREATE FLOW f AS AUTO CDC INTO target
+            |FROM STREAM(a) JOIN STREAM(b) ON a.id = b.id
+            |KEYS (id)
+            |SEQUENCE BY ts""".stripMargin)
+      },
+      condition = "PARSE_SYNTAX_ERROR",
+      sqlState = "42601",
+      parameters = Map("error" -> "'JOIN'", "hint" -> "")
+    )
+  }
+
+  // 
---------------------------------------------------------------------------
+  // Error cases: source must be a streaming query
+  // 
---------------------------------------------------------------------------
+
+  test("CREATE FLOW AS AUTO CDC INTO - non-streaming source is not allowed") {
+    checkError(
+      intercept[ParseException] {
+        parser.parsePlan(
+          """CREATE FLOW f AS AUTO CDC INTO target
+            |FROM source
+            |KEYS (id)
+            |SEQUENCE BY ts""".stripMargin)
+      },
+      condition = "_LEGACY_ERROR_TEMP_0035",
+      parameters = Map(
+        "message" -> "AUTO CDC source must be a streaming query, e.g. 
STREAM(<table>)."),
+      queryContext = Array(ExpectedContext("source", 43, 48))
+    )
+  }
+
+  test("CREATE STREAMING TABLE FLOW AUTO CDC - non-streaming source is not 
allowed") {
+    checkError(
+      intercept[ParseException] {
+        parser.parsePlan(
+          """CREATE STREAMING TABLE target
+            |FLOW AUTO CDC
+            |FROM source
+            |KEYS (id)
+            |SEQUENCE BY ts""".stripMargin)
+      },
+      condition = "_LEGACY_ERROR_TEMP_0035",
+      parameters = Map(
+        "message" -> "AUTO CDC source must be a streaming query, e.g. 
STREAM(<table>)."),
+      queryContext = Array(ExpectedContext("source", 49, 54))
+    )
+  }
+
+  // 
---------------------------------------------------------------------------
+  // Error cases: KEYS and COLUMNS only accept simple identifiers
+  // 
---------------------------------------------------------------------------
+
+  test("KEYS does not accept multipart identifiers") {
+    checkError(
+      intercept[ParseException] {
+        parser.parsePlan(
+          """CREATE FLOW f AS AUTO CDC INTO target
+            |FROM STREAM(source)
+            |KEYS (a.id)
+            |SEQUENCE BY ts""".stripMargin)
+      },
+      condition = "PARSE_SYNTAX_ERROR",
+      sqlState = "42601",
+      parameters = Map("error" -> "'.'", "hint" -> "")
+    )
+  }
+
+  test("COLUMNS include list does not accept multipart identifiers") {
+    checkError(
+      intercept[ParseException] {
+        parser.parsePlan(
+          """CREATE FLOW f AS AUTO CDC INTO target
+            |FROM STREAM(source)
+            |KEYS (id)
+            |SEQUENCE BY ts
+            |COLUMNS (a.name)""".stripMargin)
+      },
+      condition = "PARSE_SYNTAX_ERROR",
+      sqlState = "42601",
+      parameters = Map("error" -> "'.'", "hint" -> "")
+    )
+  }
+
+  test("COLUMNS * EXCEPT list does not accept multipart identifiers") {
+    checkError(
+      intercept[ParseException] {
+        parser.parsePlan(
+          """CREATE FLOW f AS AUTO CDC INTO target
+            |FROM STREAM(source)
+            |KEYS (id)
+            |SEQUENCE BY ts
+            |COLUMNS * EXCEPT (a.op)""".stripMargin)
+      },
+      condition = "PARSE_SYNTAX_ERROR",
+      sqlState = "42601",
+      parameters = Map("error" -> "'.'", "hint" -> "")
+    )
+  }
+
+  // 
---------------------------------------------------------------------------
+  // Error cases: unsupported dataset types and table features
+  // 
---------------------------------------------------------------------------
+
+  test("AUTO CDC is not supported for MATERIALIZED VIEW") {
+    val sql =
+      """CREATE MATERIALIZED VIEW target
+        |FLOW AUTO CDC
+        |FROM STREAM(source)
+        |KEYS (id)
+        |SEQUENCE BY ts""".stripMargin
+    checkError(
+      intercept[ParseException] { parser.parsePlan(sql) },
+      condition = "_LEGACY_ERROR_TEMP_0035",
+      parameters = Map(
+        "message" -> "AUTO CDC is only supported for STREAMING TABLE, not 
MATERIALIZED VIEW."),
+      queryContext = Array(ExpectedContext(sql, 0, sql.length - 1))
+    )
+  }
+
+  test("column constraints are not supported for AUTO CDC streaming table") {
+    val sql =
+      """CREATE STREAMING TABLE target (id INT PRIMARY KEY, name STRING)
+        |FLOW AUTO CDC
+        |FROM STREAM(source)
+        |KEYS (id)
+        |SEQUENCE BY ts""".stripMargin
+    checkError(
+      intercept[ParseException] { parser.parsePlan(sql) },
+      condition = "_LEGACY_ERROR_TEMP_0035",
+      parameters = Map("message" ->
+        ("Pipeline datasets do not currently support column constraints. " +
+          "Please remove any CHECK, UNIQUE, PK, and FK constraints " +
+          "specified on the pipeline dataset.")),
+      queryContext = Array(ExpectedContext(sql, 0, sql.length - 1))
+    )
+  }
+
+  test("bucketing is not supported for AUTO CDC streaming table") {
+    val sql =
+      """CREATE STREAMING TABLE target
+        |CLUSTERED BY (id) INTO 4 BUCKETS
+        |FLOW AUTO CDC
+        |FROM STREAM(source)
+        |KEYS (id)
+        |SEQUENCE BY ts""".stripMargin
+    checkError(
+      intercept[ParseException] { parser.parsePlan(sql) },
+      condition = "_LEGACY_ERROR_TEMP_0035",
+      parameters = Map("message" ->
+        ("Bucketing is not supported for CREATE STREAMING TABLE statements. " +
+          "Please remove any bucket spec specified in the statement.")),
+      queryContext = Array(ExpectedContext(sql, 0, sql.length - 1))
+    )
+  }
+
+  test("options are not supported for AUTO CDC streaming table") {
+    val sql =
+      """CREATE STREAMING TABLE target
+        |OPTIONS (key = 'value')
+        |FLOW AUTO CDC
+        |FROM STREAM(source)
+        |KEYS (id)
+        |SEQUENCE BY ts""".stripMargin
+    checkError(
+      intercept[ParseException] { parser.parsePlan(sql) },
+      condition = "_LEGACY_ERROR_TEMP_0035",
+      parameters = Map("message" ->
+        ("Options are not supported for CREATE STREAMING TABLE statements. " +
+          "Please remove any OPTIONS lists specified in the statement.")),
+      queryContext = Array(ExpectedContext(sql, 0, sql.length - 1))
+    )
+  }
+
+  test("serde is not supported for AUTO CDC streaming table") {
+    val sql =
+      """CREATE STREAMING TABLE target
+        |ROW FORMAT SERDE 'org.apache.hadoop.hive.serde2.lazy.LazySimpleSerDe'
+        |FLOW AUTO CDC
+        |FROM STREAM(source)
+        |KEYS (id)
+        |SEQUENCE BY ts""".stripMargin
+    checkError(
+      intercept[ParseException] { parser.parsePlan(sql) },
+      condition = "_LEGACY_ERROR_TEMP_0035",
+      parameters = Map("message" ->
+        ("Hive SerDe format options are not supported for " +
+          "CREATE STREAMING TABLE statements.")),
+      queryContext = Array(ExpectedContext(sql, 0, sql.length - 1))
+    )
+  }
+
+  test("location is not supported for AUTO CDC streaming table") {
+    val sql =
+      """CREATE STREAMING TABLE target
+        |LOCATION '/tmp/data'
+        |FLOW AUTO CDC
+        |FROM STREAM(source)
+        |KEYS (id)
+        |SEQUENCE BY ts""".stripMargin
+    checkError(
+      intercept[ParseException] { parser.parsePlan(sql) },
+      condition = "_LEGACY_ERROR_TEMP_0035",
+      parameters = Map("message" ->
+        ("Specifying location is not supported for CREATE STREAMING TABLE 
statements. " +
+          "The storage location for a pipeline dataset is managed by the 
pipeline itself.")),
+      queryContext = Array(ExpectedContext(sql, 0, sql.length - 1))
+    )
+  }
+
+  // 
---------------------------------------------------------------------------
+  // Error cases: deprecated / unsupported syntax
+  // 
---------------------------------------------------------------------------
+
+  test("APPLY AS TRUNCATE WHEN is not supported") {
+    checkError(
+      intercept[ParseException] {
+        parser.parsePlan(
+          """CREATE FLOW f AS AUTO CDC INTO target
+            |FROM STREAM(source)
+            |KEYS (id)
+            |APPLY AS TRUNCATE WHEN op = 'TRUNCATE'
+            |SEQUENCE BY ts""".stripMargin)
+      },
+      condition = "PARSE_SYNTAX_ERROR",
+      sqlState = "42601",
+      parameters = Map("error" -> "'TRUNCATE'", "hint" -> "")
+    )
+  }
+
+  test("IGNORE NULL UPDATES is not supported") {
+    checkError(
+      intercept[ParseException] {
+        parser.parsePlan(
+          """CREATE FLOW f AS AUTO CDC INTO target
+            |FROM STREAM(source)
+            |KEYS (id)
+            |IGNORE NULL UPDATES
+            |SEQUENCE BY ts""".stripMargin)
+      },
+      condition = "PARSE_SYNTAX_ERROR",
+      sqlState = "42601",
+      parameters = Map("error" -> "'IGNORE'", "hint" -> "")
+    )
+  }
+
+  test("STORED AS SCD TYPE 2 is not supported") {
+    checkError(
+      intercept[ParseException] {
+        parser.parsePlan(
+          """CREATE FLOW f AS AUTO CDC INTO target
+            |FROM STREAM(source)
+            |KEYS (id)
+            |SEQUENCE BY ts
+            |STORED AS SCD TYPE 2""".stripMargin)
+      },
+      condition = "PARSE_SYNTAX_ERROR",
+      sqlState = "42601",
+      parameters = Map("error" -> "'STORED'", "hint" -> "")
+    )
+  }
+
+  test("TRACK HISTORY ON is not supported") {
+    checkError(
+      intercept[ParseException] {
+        parser.parsePlan(
+          """CREATE FLOW f AS AUTO CDC INTO target
+            |FROM STREAM(source)
+            |KEYS (id)
+            |SEQUENCE BY ts
+            |TRACK HISTORY ON value1, value2""".stripMargin)
+      },
+      condition = "PARSE_SYNTAX_ERROR",
+      sqlState = "42601",
+      parameters = Map("error" -> "'TRACK'", "hint" -> "")
+    )
+  }
+}
diff --git 
a/sql/hive-thriftserver/src/test/scala/org/apache/spark/sql/hive/thriftserver/ThriftServerWithSparkContextSuite.scala
 
b/sql/hive-thriftserver/src/test/scala/org/apache/spark/sql/hive/thriftserver/ThriftServerWithSparkContextSuite.scala
index 276a266f5d16..4e8f117dc8a5 100644
--- 
a/sql/hive-thriftserver/src/test/scala/org/apache/spark/sql/hive/thriftserver/ThriftServerWithSparkContextSuite.scala
+++ 
b/sql/hive-thriftserver/src/test/scala/org/apache/spark/sql/hive/thriftserver/ThriftServerWithSparkContextSuite.scala
@@ -214,7 +214,7 @@ trait ThriftServerWithSparkContextSuite extends 
SharedThriftServer {
       val sessionHandle = client.openSession(user, "")
       val infoValue = client.getInfo(sessionHandle, 
GetInfoType.CLI_ODBC_KEYWORDS)
       // scalastyle:off line.size.limit
-      assert(infoValue.getStringValue == 
"ADD,AFTER,AGGREGATE,ALIGN,ALL,ALTER,ALWAYS,ANALYZE,AND,ANTI,ANY,ANY_VALUE,APPROX,ARCHIVE,ARRAY,AS,ASC,ASENSITIVE,AT,ATOMIC,AUTHORIZATION,BEGIN,BERNOULLI,BETWEEN,BIGINT,BIN,BINARY,BINDING,BIN_DISTRIBUTE_RATIO,BIN_END,BIN_START,BOOLEAN,BOTH,BUCKET,BUCKETS,BY,BYTE,CACHE,CALL,CALLED,CASCADE,CASE,CAST,CATALOG,CATALOGS,CHANGE,CHANGES,CHAR,CHARACTER,CHECK,CLEAR,CLOSE,CLUSTER,CLUSTERED,CODEGEN,COLLATE,COLLATION,COLLATIONS,COLLECTION,COLUMN,COLUMNS,COMMEN
 [...]
+      assert(infoValue.getStringValue == 
"ADD,AFTER,AGGREGATE,ALIGN,ALL,ALTER,ALWAYS,ANALYZE,AND,ANTI,ANY,ANY_VALUE,APPLY,APPROX,ARCHIVE,ARRAY,AS,ASC,ASENSITIVE,AT,ATOMIC,AUTHORIZATION,AUTO,BEGIN,BERNOULLI,BETWEEN,BIGINT,BIN,BINARY,BINDING,BIN_DISTRIBUTE_RATIO,BIN_END,BIN_START,BOOLEAN,BOTH,BUCKET,BUCKETS,BY,BYTE,CACHE,CALL,CALLED,CASCADE,CASE,CAST,CATALOG,CATALOGS,CDC,CHANGE,CHANGES,CHAR,CHARACTER,CHECK,CLEAR,CLOSE,CLUSTER,CLUSTERED,CODEGEN,COLLATE,COLLATION,COLLATIONS,COLLECTION,COLUMN
 [...]
       // scalastyle:on line.size.limit
     }
   }


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to