Files
upstream-sqlc/internal/goldeneye/mysql/trace.go
upstream-mirror 0f4df23290
Some checks failed
kotlin / test (push) Has been skipped
python / test (push) Has been skipped
typescript / test (push) Has been skipped
go / build darwin/amd64 (push) Has been cancelled
go / build linux/amd64 (push) Has been cancelled
go / build windows/amd64 (push) Has been cancelled
go / build darwin/arm64 (push) Has been cancelled
go / build linux/arm64 (push) Has been cancelled
go / build windows/arm64 (push) Has been cancelled
go / test (push) Has been cancelled
go / vuln_check (push) Has been cancelled
snapshot of https://github.com/sqlc-dev/sqlc.git (main, 2026-09-16; blobs > 131072B omitted)
2026-09-16 02:17:34 +05:00

88 lines
2.5 KiB
Go

package mysql
import (
"encoding/json"
"fmt"
"sort"
"strings"
)
// trace is what the optimizer trace says about a statement: the text of
// each query block after name resolution and before optimisation, by the
// number MySQL gives the block, and the block each derived table or CTE is
// materialised from.
//
// The trace is the one place MySQL prints a query block as it was resolved
// rather than as it was optimised. The note EXPLAIN leaves is printed
// after optimisation, and a lookup on a unique key against an empty table
// folds to `NULL = (@x)` there, losing the column the parameter was
// compared with; the expanded_query of the block still has it.
type trace struct {
blocks map[int]text
derived map[string]int
}
// parseTrace reads the JSON of the OPTIMIZER_TRACE table's TRACE column.
func parseTrace(blob string) (trace, error) {
tr := trace{blocks: map[int]text{}, derived: map[string]int{}}
if strings.TrimSpace(blob) == "" {
return tr, nil
}
var v any
if err := json.Unmarshal([]byte(blob), &v); err != nil {
return tr, fmt.Errorf("reading the optimizer trace: %w", err)
}
tr.walk(v, 0)
return tr, nil
}
// walk collects the first expanded_query printed for each block — the one
// join_preparation prints, before join_optimization prints its rewrite —
// and the derived tables. Lists are walked in order and objects in key
// order, so the result does not depend on map iteration.
func (tr *trace) walk(v any, sel int) {
switch v := v.(type) {
case map[string]any:
if n, ok := v["select#"].(float64); ok {
sel = int(n)
}
if d, ok := v["derived"].(map[string]any); ok {
if table, ok := d["table"].(string); ok {
if n, ok := d["select#"].(float64); ok {
tr.derived[strings.Trim(table, "` ")] = int(n)
}
}
}
if q, ok := v["expanded_query"].(string); ok {
if _, seen := tr.blocks[sel]; !seen {
tr.blocks[sel] = tokenize(q)
}
}
keys := make([]string, 0, len(v))
for k := range v {
keys = append(keys, k)
}
sort.Strings(keys)
for _, k := range keys {
tr.walk(v[k], sel)
}
case []any:
for _, c := range v {
tr.walk(c, sel)
}
}
}
// order lists the block numbers innermost first: a block's text includes
// its subqueries' text, and a subquery is numbered after the block it is
// in, so the block a parameter belongs to is the highest-numbered block
// whose text has it.
func (tr *trace) order() []int {
sels := make([]int, 0, len(tr.blocks))
for sel := range tr.blocks {
sels = append(sels, sel)
}
sort.Sort(sort.Reverse(sort.IntSlice(sels)))
return sels
}