Skip to content

[Bug]: empty CTE body is accepted and panics in tree.CTE.Format (CTAS / prepare) #30146

Closed

Description

@iamlinjunhong

Is there an existing issue for the same bug?

  • I have checked the existing issues.

Branch Name

main

Commit ID

2ce4e58

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).Format

Minimal 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 dereference

Additional 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

  1. MatrixAdventurer commented on Oct 14, 2026

    @MatrixAdventurer
    Contributor

    Opened fix PR: #30151

    Root cause: stmt EMPTY alternative inside AS '(' stmt ')' produced CTE.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.

    本操作由 LW 授权的 Grok Bot 智能体执行

  2. XuPeng-SH commented on Oct 18, 2026

    @XuPeng-SH
    Contributor

    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全部高覆盖实测完成。

  3. Ariznawlll commented on Oct 19, 2026

    @Ariznawlll
    Collaborator

    测试版本

    • 分支:官方 main
    • commit:2ad1d7e5c2baafdee0b8fa097ce02ca32feb3a7b,包含修复 PR #30151 的 merge commit 5f8d38f1d38b1d9bb80956691c05e4421df9fd34(已核对祖先关系)。
    • 部署方式:macOS/arm64,从上述隔离 worktree 重新编译 mo-service,在新临时数据目录运行 1 CN / 1 TN / 1 LogService;MySQL 协议 127.0.0.1:6001。未使用共享集群。
    • 测试日期:2026-10-19(UTC)。

    原问题验证

    原 WITH a AS () SELECT 1 连续 3/3 返回语法拒绝 1064,不再产生 tree.CTE.Format panic。原 CTAS CREATE 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。

    回归测试

    测试证据

    上述 MOTR 成对文件含精确返回码、错误文本、失败后目录计数与合法 CTAS 读回值;PR #247 的检查记录 可复核。测试用临时数据库已清理。

    测试结论

    测试通过。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

kind/bugSomething isn't working

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions