parseTiDBStatementsOmni is the post-flip ParseStatementsFunc for TiDB (registered in tidb.go init). Implements Option B per plan §1.5.0 invariant #8: omni first, pingcap fallback per statement on omni parse failure. The review never hard-fails at the dispatcher level on a Tier 4 grammar gap — custom
(statement string)
| 33 | // A for the migration window; less informative than the future state where |
| 34 | // omni grammar is complete enough to drop the fallback. |
| 35 | func parseTiDBStatementsOmni(statement string) ([]base.ParsedStatement, error) { |
| 36 | stmts, err := base.SplitMultiSQL(storepb.Engine_TIDB, statement) |
| 37 | if err != nil { |
| 38 | return nil, err |
| 39 | } |
| 40 | |
| 41 | var result []base.ParsedStatement |
| 42 | for _, stmt := range stmts { |
| 43 | if stmt.Empty { |
| 44 | result = append(result, base.ParsedStatement{Statement: stmt}) |
| 45 | continue |
| 46 | } |
| 47 | |
| 48 | // Attempt order: omni first (sub-contract). Pingcap-first would |
| 49 | // defeat the architectural intent — post-flip, omni is canonical; |
| 50 | // pingcap is the safety net. |
| 51 | list, omniErr := ParseTiDBOmni(stmt.Text) |
| 52 | if omniErr == nil { |
| 53 | if list == nil || len(list.Items) == 0 { |
| 54 | // Omni succeeded but produced no items (e.g. comment-only |
| 55 | // statement that survived the splitter). Preserve the |
| 56 | // statement position with a nil AST, matching pre-flip |
| 57 | // parseTiDBStatements semantics. |
| 58 | result = append(result, base.ParsedStatement{Statement: stmt}) |
| 59 | continue |
| 60 | } |
| 61 | for _, node := range list.Items { |
| 62 | result = append(result, base.ParsedStatement{ |
| 63 | Statement: stmt, |
| 64 | AST: &OmniAST{ |
| 65 | Node: node, |
| 66 | Text: stmt.Text, |
| 67 | StartPosition: stmt.Start, |
| 68 | }, |
| 69 | }) |
| 70 | } |
| 71 | continue |
| 72 | } |
| 73 | |
| 74 | // Omni rejected — try Option B fallback to pingcap. |
| 75 | ast, fallbackErr := parsePingCapSingleStatement(stmt) |
| 76 | if fallbackErr != nil { |
| 77 | // Both engines reject. Don't increment the fallback counter — |
| 78 | // this is genuine bad SQL, not an omni grammar gap. Inflating |
| 79 | // the counter (especially the "unknown" bucket) on bad-SQL |
| 80 | // inputs would skew the Option B → A retirement-gate signal: |
| 81 | // after omni grammar is complete, malformed customer SQL |
| 82 | // would keep the counter non-zero and the gate would never |
| 83 | // fire. Surface omni's error so customer-facing expectations |
| 84 | // track the eventual Option A state (Q2 design choice — see |
| 85 | // plans/2026-04-23-omni-tidb-completion-plan.md §1.5.0 |
| 86 | // invariant #8 + dispatcher_test.go regression pin). |
| 87 | return nil, convertOmniParseError(omniErr, stmt) |
| 88 | } |
| 89 | |
| 90 | // Fallback succeeded — record the omni gap that pingcap bridged. |
| 91 | // Counter measures "omni rejected AND pingcap accepted" — the |
| 92 | // cases that genuinely justify Option B and drive the retirement |