{"record":{"id":"717f3216876b75df","repo":"vitessio/vitess","slug":"bug-tried-to-replace-sqlnode-on-rootnode","errorCode":null,"errorMessage":"[BUG] tried to replace 'SQLNode' on 'RootNode'","messagePattern":"\\[BUG\\] tried to replace 'SQLNode' on 'RootNode'","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"go/vt/sqlparser/ast_rewrite.go","lineNumber":11117,"sourceCode":"func (a *application) rewriteRootNode(parent SQLNode, node RootNode, replacer replacerFunc) bool {\n\tif a.pre != nil {\n\t\ta.cur.replacer = replacer\n\t\ta.cur.parent = parent\n\t\ta.cur.node = node\n\t\tkontinue := !a.pre(&a.cur)\n\t\tif a.cur.revisit {\n\t\t\ta.cur.revisit = false\n\t\t\treturn a.rewriteSQLNode(parent, a.cur.node, replacer)\n\t\t}\n\t\tif kontinue {\n\t\t\treturn true\n\t\t}\n\t}\n\tif a.collectPaths {\n\t\ta.cur.current.AddStep(uint16(RootNodeSQLNode))\n\t}\n\tif !a.rewriteSQLNode(node, node.SQLNode, func(newNode, parent SQLNode) {\n\t\tpanic(\"[BUG] tried to replace 'SQLNode' on 'RootNode'\")\n\t}) {\n\t\treturn false\n\t}\n\tif a.collectPaths {\n\t\ta.cur.current.Pop()\n\t}\n\tif a.post != nil {\n\t\ta.cur.replacer = replacer\n\t\ta.cur.parent = parent\n\t\ta.cur.node = node\n\t\tif !a.post(&a.cur) {\n\t\t\treturn false\n\t\t}\n\t}\n\treturn true\n}\n\n// Function Generation Source: PtrToStructMethod","sourceCodeStart":11099,"sourceCodeEnd":11135,"githubUrl":"https://github.com/vitessio/vitess/blob/01a25a7d176f94613b8d59d799f438380a8760e4/go/vt/sqlparser/ast_rewrite.go#L11099-L11135","documentation":"In the generated AST rewriter, RootNode.SQLNode is the root placeholder that cannot be replaced: the generated rewrite callback unconditionally panics if a rewrite attempts to swap it. This is a defensive invariant — rewrite functions must never return a replacement for the root wrapper itself.","triggerScenarios":"Supplying a Rewrite function (or using RewriteOptions) that returns a non-nil replacement when invoked with parent=RootNode, e.g. an unconditional `return newExpr, true` in the callback instead of only replacing targeted child nodes.","commonSituations":"Custom rewriters that replace nodes without checking the parent type; rewriters that match on the root SQLNode wrapper produced by safeRewrite; forked generated code after a parser regen.","solutions":["In your rewrite callback, only return a replacement for the specific node kinds you intend to change — never for the RootNode wrapper","Add a type/parent check: skip replacement when parent is the root (e.g. only replace *sqlparser.TableName, *ColName, etc.)","Return (node, false) (or the original node) from the callback for nodes you do not want to alter","Regenerate with `make codegen` if you forked ast_rewrite.go and the invariant drifted"],"exampleFix":"// before\nfunc repl(node, parent sqlparser.SQLNode) (sqlparser.SQLNode, bool) {\n    return newExpr, true // replaces everything, incl. root\n}\n// after\nfunc repl(node, parent sqlparser.SQLNode) (sqlparser.SQLNode, bool) {\n    if _, ok := node.(*sqlparser.TableName); !ok {\n        return node, false\n    }\n    return newExpr, true\n}","handlingStrategy":"try-catch","validationCode":"func repl(node, parent sqlparser.SQLNode) (sqlparser.SQLNode, bool) {\n    // never replace when the node is the root wrapper\n    if node == nil {\n        return node, false\n    }\n    switch node.(type) {\n    case *sqlparser.TableName, *sqlparser.ColName:\n        return replaceIfMatched(node)\n    default:\n        return node, false\n    }\n}","typeGuard":null,"tryCatchPattern":"err := func() (err error) {\n    defer func() {\n        if r := recover(); r != nil {\n            err = fmt.Errorf(\"rewrite panicked: %v\", r)\n        }\n    }()\n    return sqlparser.SafeRewrite(stmt, opts, repl)\n}()","preventionTips":["Only return replacements for concrete node types you target","Never return (newNode, true) unconditionally from a rewrite callback","Skip identifier and root wrapper nodes in callbacks","Test rewriters against full statements, not isolated expressions"],"tags":["sqlparser","ast-rewrite","codegen","panic","bug"],"backgroundTag":"invalid-ast-rewrite-replacement","analyzedSha":"01a25a7d176f94613b8d59d799f438380a8760e4","analyzedAt":"2026-09-01T17:28:30.605Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}