Files
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

237 lines
5.7 KiB
Go

package clickhouse
import (
"fmt"
"regexp"
"strconv"
"strings"
)
// treeNode is one line of EXPLAIN QUERY TREE output. Lines of the form
// `KIND id: N, key: value, ...` are nodes with a kind and attributes; every
// other line (section headers such as PROJECTION or ARGUMENTS, and the
// `name Type` lines under PROJECTION COLUMNS) keeps only its text.
type treeNode struct {
kind string
id int
attrs map[string]string
text string
parent *treeNode
children []*treeNode
}
// queryTree is a parsed EXPLAIN QUERY TREE dump.
type queryTree struct {
root *treeNode
byID map[int]*treeNode
}
var nodeLineRe = regexp.MustCompile(`^([A-Z_]+) id: (\d+)(?:, (.*))?$`)
// parseQueryTree builds the tree from the dump's lines, using the two-space
// indentation to recover nesting.
func parseQueryTree(lines []string) (*queryTree, error) {
root := &treeNode{id: -1, text: "<root>"}
t := &queryTree{root: root, byID: map[int]*treeNode{}}
stack := []*treeNode{root}
for _, line := range lines {
if strings.TrimSpace(line) == "" {
continue
}
indent := len(line) - len(strings.TrimLeft(line, " "))
if indent%2 != 0 {
return nil, fmt.Errorf("query tree: unexpected indentation in %q", line)
}
depth := indent/2 + 1
if depth > len(stack) {
return nil, fmt.Errorf("query tree: line %q is nested too deeply", line)
}
stack = stack[:depth]
parent := stack[len(stack)-1]
n := &treeNode{id: -1, text: line[indent:], parent: parent}
if m := nodeLineRe.FindStringSubmatch(n.text); m != nil {
n.kind = m[1]
n.id, _ = strconv.Atoi(m[2])
n.attrs = parseAttrs(m[3])
t.byID[n.id] = n
}
parent.children = append(parent.children, n)
stack = append(stack, n)
}
return t, nil
}
var attrKeyRe = regexp.MustCompile(`^, ([a-z_]+): `)
// parseAttrs splits `key: value, key: value` where values may themselves
// contain commas inside parentheses or quotes, as types and constants do.
func parseAttrs(s string) map[string]string {
attrs := map[string]string{}
if s == "" {
return attrs
}
// Positions at which a new `, key: ` begins at top level.
var cuts []int
depth := 0
inQuote := false
for i := 0; i < len(s); i++ {
switch c := s[i]; {
case c == '\'':
inQuote = !inQuote
case inQuote:
case c == '(':
depth++
case c == ')':
depth--
case c == ',' && depth == 0 && attrKeyRe.MatchString(s[i:]):
cuts = append(cuts, i)
}
}
cuts = append(cuts, len(s))
start := 0
for _, cut := range cuts {
pair := s[start:cut]
if key, value, ok := strings.Cut(pair, ": "); ok {
attrs[key] = value
}
start = cut + 2
}
return attrs
}
// section returns the child section header of a node, such as PROJECTION
// or JOIN TREE, or nil.
func (n *treeNode) section(name string) *treeNode {
for _, c := range n.children {
if c.kind == "" && c.text == name {
return c
}
}
return nil
}
// list returns the nodes of the LIST under a section header.
func (n *treeNode) list() []*treeNode {
if n == nil {
return nil
}
for _, c := range n.children {
if c.kind == "LIST" {
return c.children
}
}
return nil
}
// firstQuery returns the QUERY node describing a result set: the node itself
// or, for a UNION, its first branch, whose projection names the union's
// columns.
func firstQuery(n *treeNode) *treeNode {
for depth := 0; n != nil && depth < 32; depth++ {
switch n.kind {
case "QUERY":
return n
case "UNION":
n = firstNode(n.section("QUERIES").list())
default:
return nil
}
}
return nil
}
func firstNode(nodes []*treeNode) *treeNode {
if len(nodes) == 0 {
return nil
}
return nodes[0]
}
// projection returns the output column names of a query and the expression
// node producing each, in order.
func projection(q *treeNode) (names []string, nodes []*treeNode) {
q = firstQuery(q)
if q == nil {
return nil, nil
}
if cols := q.section("PROJECTION COLUMNS"); cols != nil {
for _, c := range cols.children {
names = append(names, projectedName(c.text))
}
}
return names, q.section("PROJECTION").list()
}
// projectedName strips the trailing type from a `name Type` line. The type
// is a single token that may carry a parenthesised argument list; the name
// may contain spaces of its own, as `plus(id, 1)` does.
func projectedName(line string) string {
end := len(line)
if strings.HasSuffix(line, ")") {
depth := 0
for end > 0 {
end--
if line[end] == ')' {
depth++
} else if line[end] == '(' {
depth--
if depth == 0 {
break
}
}
}
}
for end > 0 && isWordByte(line[end-1]) {
end--
}
return strings.TrimSpace(line[:end])
}
// sourceTable resolves a COLUMN node to the table it reads from, following
// column references through subqueries and CTEs. Columns computed by an
// expression have no source and yield "".
func (t *queryTree) sourceTable(col *treeNode) string {
for depth := 0; col != nil && col.kind == "COLUMN" && depth < 32; depth++ {
id, err := strconv.Atoi(col.attrs["source_id"])
if err != nil {
return ""
}
src := t.byID[id]
if src == nil {
return ""
}
switch src.kind {
case "TABLE":
name := src.attrs["table_name"]
if i := strings.LastIndexByte(name, '.'); i >= 0 {
name = name[i+1:]
}
return name
case "QUERY", "UNION":
// The column is one of the subquery's output columns; keep
// following whatever expression produces it there.
want := col.attrs["column_name"]
names, nodes := projection(src)
col = nil
for i, name := range names {
if name == want && i < len(nodes) {
col = nodes[i]
break
}
}
default:
return ""
}
}
return ""
}
// childrenOrNil returns a node's children, tolerating a nil node.
func (n *treeNode) childrenOrNil() []*treeNode {
if n == nil {
return nil
}
return n.children
}