Repository navigation
[Bug]: empty CTE body is accepted and panics in tree.CTE.Format (CTAS / prepare) #30146
Description
Is there an existing issue for the same bug?
- I have checked the existing issues.
Branch Name
main
Commit ID
Other Environment Information
- Hardware parameters: Apple Silicon (arm64), 10 cores
- OS type: macOS 15.5 (Darwin arm64), Go 1.27.1
- Others: single-node mo-service (LOG/TN/CN in one process) launched from etc/launch with a LOOM_TEST_PORT_OFFSET port shift; connected with the MySQL 9.7.1 client
Actual Behavior
A query that uses an empty common-table-expression body, e.g. with a as () select 1, is accepted by the parser, which builds a tree.CTE whose Stmt field is nil. MySQL 8.0 rejects the same text with a syntax error.
Once the AST is formatted, MatrixOne panics with a nil pointer dereference. Any statement whose plan build formats the SELECT (for example CTAS or PREPARE) surfaces the panic to the client as an internal error:
mysql> create table t2 as with a as () select 1;
ERROR 20101 (HY000) at line 1: internal error: panic runtime error: invalid memory address or nil pointer dereference:
github.com/matrixorigin/matrixone/pkg/sql/parsers/tree.(*CTE).Format
/.../pkg/sql/parsers/tree/with.go:46
github.com/matrixorigin/matrixone/pkg/sql/parsers/tree.(*With).Format
/.../pkg/sql/parsers/tree/with.go:31
github.com/matrixorigin/matrixone/pkg/sql/parsers/tree.(*Select).Format
/.../pkg/sql/parsers/tree/select.go:57
github.com/matrixorigin/matrixone/pkg/sql/plan.buildTableDefs
/.../pkg/sql/plan/build_ddl.go:4063
mysql> prepare s from 'with a as () select 1';
ERROR 20101 (HY000) at line 1: internal error: panic runtime error: invalid memory address or nil pointer dereference:
github.com/matrixorigin/matrixone/pkg/sql/parsers/tree.(*CTE).Format (with.go:46)
github.com/matrixorigin/matrixone/pkg/sql/parsers/tree.ParameterCount (format.go:56)
github.com/matrixorigin/matrixone/pkg/sql/plan.buildPrepare (build_dcl.go:111)
The panic can also be triggered directly with the public formatter tree.StringWithOpts(stmt, dialect.MYSQL, ...) on the AST returned by mysql.Parse("with a as () select 1", 1).
Expected Behavior
with a as () select 1 must be rejected at parse time with a syntax error (MySQL: ERROR 1064 ... near '() select 1'). It must never produce a CTE whose statement is nil, and formatting any AST must not panic.
Steps to Reproduce
create database if not exists ctebug;
use ctebug;
create table t2 as with a as () select 1; -- ERROR 20101: panic ... tree.(*CTE).Format
prepare s from 'with a as () select 1'; -- ERROR 20101: panic ... tree.(*CTE).FormatMinimal parser-level reproduction (Go):
stmts, err := mysql.Parse(context.Background(), "with a as () select 1", 1) // err == nil
_ = tree.StringWithOpts(stmts[0], dialect.MYSQL,
tree.WithQuoteIdentifier(), tree.WithSingleQuoteString()) // panics: nil pointer dereferenceAdditional information
Root cause: the grammar rule common_table_expr accepts an empty statement for the CTE body:
common_table_expr:
ident column_list_opt AS '(' stmt ')'
stmt has an empty production (stmt: /* EMPTY */ -> tree.Statement(nil)), so with a as () ... produces a tree.CTE with Stmt == nil and no validation. tree.CTE.Format then calls the statement's Format without a nil check:
func (node *CTE) Format(ctx *FmtCtx) {
node.Name.Format(ctx)
ctx.WriteString(" as (")
node.Stmt.Format(ctx) // panics when node.Stmt == nil
ctx.WriteString(")")
}Relevant locations: pkg/sql/parsers/dialect/mysql/mysql_sql.y (common_table_expr line 7281, empty stmt line 1146) and pkg/sql/parsers/tree/with.go:46. A fix should reject the empty CTE body during parsing (preferred, matches MySQL) and/or guard CTE.Format/With.Format against nil statements. with a as () select * from a already fails with a different, non-panic error (unexpected statement: ''), confirming the nil statement propagates into later stages.
Found by Loom bug hunt (module: sql-parser; tests: unit, black-box)
Activity
MatrixAdventurer commentedon Oct 14, 2026
Opened fix PR: #30151
Root cause:
stmtEMPTY alternative insideAS '(' stmt ')'producedCTE.Stmt == nil; CTAS/PREPARE then panicked in(*CTE).Format.Fix: reject nil CTE body at parse time (MySQL-compatible syntax error). E2E on
a53390c5: main panics on PREPARE/CTAS; PR returns 1064/1105; valid CTEs and BVT prepare/cte/recursive_cte all green.fix(parser): reject empty CTE bodies to avoid Format panic
fix(parser): reject empty CTE bodies to avoid Format panic (#30151)
XuPeng-SH commentedon Oct 18, 2026
continue20 独立功能验证(2026-10-19),修复 PR #30151。
实测main完整SHA:
2508ce7029b03e6bb3ac0bd8e66a994d12593664;Linux/amd64,Go1.27.1,CPU/CGo。复用同SHA的已构建原生产物并经仓库mo-cgo-test入口验证;隔离worktree,诊断测试通过Go overlay加入,未修改产品源代码。前轮本任务解析器6个invalid/5个valid用例已经实际通过;本轮补齐真实MySQL两CN公开路径:空CTE的SELECT、PREPARE、CTAS全部返回语法拒绝且无panic,每次失败后SELECT 1成功;合法CTAS生成并读回42。
原始证据目录
/workspace/work/pr-audit-continue20:public-matrix.log,前轮continue19/logs/morpc-parser.log,每组.exit均0。命令包括.agents/skills/mo-dev/scripts/mo-cgo-test -p 1 -count=1 -v -timeout=10m ./pkg/sql/colexec/icebergdelete ./pkg/vm/engine/tae/blockio(normal含coverprofile,race另加-race);公开SQL为MO_TEST_CLUSTER_TCP_ONLY=1 .agents/skills/mo-dev/scripts/mo-cgo-test -overlay=/workspace/work/pr-audit-continue20/public-overlay.json -p 1 -v -count=1 -timeout=15m -run '^TestAudit20PublicMatrix$' ./pkg/tests/issues,扩展S3与scalar警告各有独立选择和日志。具体命令/选择范围及覆盖文件随证据保留;没有把其他未选用例当成执行。main报告范围通过,转 @Ariznawlll 测试,移除原开发指派。 本条不表示最近100个PR全部高覆盖实测完成。
Ariznawlll commentedon Oct 19, 2026
测试版本
main2ad1d7e5c2baafdee0b8fa097ce02ca32feb3a7b,包含修复 PR #30151 的 merge commit5f8d38f1d38b1d9bb80956691c05e4421df9fd34(已核对祖先关系)。127.0.0.1:6001。未使用共享集群。原问题验证
原
WITH a AS () SELECT 1连续 3/3 返回语法拒绝1064,不再产生tree.CTE.Formatpanic。原 CTASCREATE TABLE bad_cte AS WITH a AS () SELECT 1连续 3/3 返回1064;每轮查询目录均确认bad_cte数量为 0。原 SQL PREPARE 路径连续 3/3 返回包含empty common table expression body的语法错误1105,不再返回内部 panic。上述两类错误后新连接SELECT 1均返回 1。Happy Path 覆盖
合法
WITH a AS (SELECT 42 AS x)的 CTAS 连续 3/3 成功,读回x=42。解析器原有TestEmptyCTEBodyRejected|TestNonEmptyCTEBodyStillParses在相同 SHA 上-count=3通过。Unhappy Path 覆盖
空 CTE 分别经直接 SELECT、CTAS、PREPARE 拒绝;CTAS 失败未创建目标表,连接保持可用。未把
1105误写成1064。白盒验证
Go 解析器上述两项定向测试
-count=3通过;本问题为确定性解析拒绝,不涉及并发竞态,本轮未运行-race。回归测试
t/issue_30146_empty_cte_rejection.test与r/issue_30146_empty_cte_rejection.result,汇总 PR #247,本地定向 3/3(36/36 语句);与同批另 3 组用例并行复测 12/12 通过。157e741f24d740fcfd642bf0a85f52e413854adc的 4 项离线检查成功;完整 14_issue_regression suite 与集成 CI 未执行,PR 尚未合入,此处不声称已通过。测试证据
上述 MOTR 成对文件含精确返回码、错误文本、失败后目录计数与合法 CTAS 读回值;PR #247 的检查记录 可复核。测试用临时数据库已清理。
测试结论
测试通过。