#21138·vitess

Feature Request: parse the CREATE TABLE source forms the grammar only partially accepts (parenthesized SELECT, AS TABLE, EXCEPT/INTERSECT)

Author: mattlordCreated Sep 18, 2026Updated Sep 18, 2026
LabelsType: EnhancementComponent: Query Serving

Overview of the Issue

The SQL parser accepts several CREATE TABLE forms only partially. When the grammar hits syntax it does not know after CREATE TABLE <name>, Parse2 (go/vt/sqlparser/parser.go) keeps the *CreateTable node with just the name and marks it FullyParsed=false; TableSpec, OptLike and Select are all nil. vttablet then forwards the client's raw text to MySQL.

These MySQL-valid forms are among them, and each copies rows from a source table the AST never records:

create table ct (select id from src)                                  -- parenthesized query as the body
create table ct as table src                                          -- TABLE statement as the source
create table ct as select id from a except select id from b           -- EXCEPT / INTERSECT source
create table ct as select * from (table src) d                        -- TABLE inside a derived table

(EXCEPT is worse than the others: the parser keeps a truncated Select covering only the first arm, so a consumer that trusted it would see a and miss b.)

Under strict table ACL, #21139 (stacked on #21053) treats every partially parsed CREATE TABLE as having an undetermined table set and fails closed on it for callers outside the exempt ACL, because the planner cannot tell a row-copying form from a valid statement in syntax the grammar simply lacks — create table t (id int) start transaction arrives with the same empty node. That is correct but coarse: it denies legitimate CREATE TABLE syntax the grammar does not cover, and it depends on the fallback behaving the same way forever.

The better end state is for the grammar to parse these sources so they become ordinary CreateTable.Select values that the permission walker, the schema tracker and Online DDL can all see. Concretely:

  • create_table_prefix ... '(' select_statement ')' and create_table_prefix ... table_spec? '(' select_statement ')'
  • AS/opt_as TABLE table_name as a Select-like source (MySQL's TABLE statement)
  • EXCEPT and INTERSECT in query_expression (MySQL 8.0.31+), which also fixes the truncated Select
  • TABLE table_name as a derived-table body

Once these parse fully, the !IsFullyParsed() guard in planbuilder.BuildPermissions stops firing for them and the over-denial narrows to genuinely unknown syntax. The create table t golden in planbuilder/testdata/ddl_cases.txt and the partial-parse cases in TestBuildPermissions document the current classification.

Reproduction Steps

  1. Parse any of the four statements above with sqlparser.NewTestParser().Parse(...).
  2. Observe a *sqlparser.CreateTable with IsFullyParsed() == false and Select == nil (or a one-arm Select for the EXCEPT form), while MySQL 8.0.46 accepts the statement and copies the rows.
  3. With --queryserver-config-strict-table-acl, a non-exempt caller is denied with DDL command denied to user '...' for a table set that cannot be determined (ACL check error), even with full grants on every table involved.

Binary Version

bash
vttablet --version
Version: 25.0.0-SNAPSHOT (Git revision 1cf37324b4 branch 'main')

Operating System and Environment details

bash
Any. Verified against the parser and MySQL 8.0.46 on macOS arm64.

Log Fragments

WRN sqlparser/parser.go:84 ignoring error parsing DDL 'create table ct (select id from src)': syntax error at position 24 near 'select'
WRN sqlparser/parser.go:84 ignoring error parsing DDL 'create table ct as table src': syntax error at position 25 near 'table'

Related

This is one of a set of changes that came out of one investigation into what a stored procedure, or any statement whose tables the planner never sees, can do on a vttablet. They reference each other so the whole picture is reachable from any one of them.

Table ACL — GHSA-w6mx-2f8x-pqf4

  • #21053 — fail closed under strict table ACL for statements whose tables the parser discards (DO, CALL, REPAIR, OPTIMIZE, LOAD DATA)
  • #21139 — stacked on #21053: check the reads embedded in statements the planner does parse (CREATE TABLE ... AS SELECT, EXPLAIN ANALYZE, SHOW ... WHERE, SET), reject connection settings that carry a subquery, and have a targeted vtgate session store a SET's value rather than its expression
  • #21134 — still open: a stored function invoked inside an expression runs its body outside the table ACL
  • #21138 — (this issue) still open: parse the CREATE TABLE source forms the grammar only partially accepts, which #21139 has to deny outright today
  • #21140 — still open: ALTER/REVERT VITESS_MIGRATION derive no permissions, so any authenticated caller can control Online DDL migrations; split out of #21053's review

Stored-procedure session residue

  • #21046 — procedure-body session state (SET SESSION, temp tables) persists on pooled connections after CALL
  • #21062 — fixes #21046 by discarding the pooled connection after CALL
  • #21063 — still open: the same residue on a transaction connection recycled at commit
  • #21066 — still open: the pooled-connection baseline differs between fresh and settings-reset connections on servers using init_connect