// Canonical identity for a decorrelated COUNT{} side table: a byte string // over the subquery's pattern and WHERE with the anchor and group // variables substituted by positional markers, so two sibling subqueries // differing only in what the outer query names those endpoints (BI Q8's // C1(person)/C1(friend)) canonicalize identically and share one table per // anchor node. Fail-closed: any construct the encoder does not cover // yields no identity and the node keeps a private table -- sharing is an // optimization, never a semantic commitment. Parameters encode by slot or // name, which is sound because tables are shared only within one // execution's Ctx, where parameter values are fixed.
package compile import ( ) // decorCanon renders the canonical identity, "" when the shape has none. // The substitution markers use bytes no identifier contains. func decorCanon( *ast.Pattern, ast.Expr, , string) string { var strings.Builder := map[string]string{: "\x01", : "\x02"} if !canonPattern(&, , ) { return "" } .WriteByte('|') if != nil && !canonExpr(&, , ) { return "" } return .String() } // canonAtom writes a length-prefixed identifier-ish string. Backtick // delimiting lets a label, type, key, variable, or parameter name carry // ANY byte, so no separator byte is safe as a frame; the length prefix // makes every atom self-delimiting and the whole encoding injective. func canonAtom( *strings.Builder, string) { .WriteString(strconv.Itoa(len())) .WriteByte(':') .WriteString() } // canonAtoms writes a length-prefixed list of atoms (the count guards // against a list/adjacent-field ambiguity). func canonAtoms( *strings.Builder, []string) { .WriteString(strconv.Itoa(len())) .WriteByte('[') for , := range { canonAtom(, ) } .WriteByte(']') } // canonVar writes a variable reference under the substitution map. func canonVar( *strings.Builder, string, map[string]string) { if , := []; { .WriteString() return } .WriteByte('v') canonAtom(, ) } // canonPattern encodes a linear pattern: nodes and hops in written order. func canonPattern( *strings.Builder, *ast.Pattern, map[string]string) bool { if !canonNode(, &.Start, ) { return false } for := range .Hops { := &.Hops[] if .Rel.Length != nil { return false // decor never admits these; fail closed anyway } if .Rel.Where != nil { // Desugar conjoins the inline predicate onto the clause WHERE // and clears this field, so it should never be seen here -- // but an equivalence used to ERASE an operand must refuse any // row-selecting field it does not compare (two patterns // differing only in an inline WHERE are not interchangeable). return false } .WriteByte('-') .WriteByte(byte('0' + int(.Rel.Dir))) canonVar(, .Rel.Var, ) canonAtoms(, .Rel.Types) if len(.Rel.Props) > 0 || len(.Rel.PropExprs) > 0 { return false } .WriteByte('-') if !canonNode(, &.Node, ) { return false } } return true } // canonNode encodes one node pattern: variable, labels, literal props. func canonNode( *strings.Builder, *ast.NodePat, map[string]string) bool { .WriteByte('(') canonVar(, .Var, ) if .LabelExpr != nil { if !canonLabelExpr(, .LabelExpr) { return false } } else { .WriteByte(':') canonAtoms(, .Labels) } if len(.PropExprs) > 0 { return false } if .Where != nil { return false // inline node predicate: same fail-closed rule as rels } for , := range .Props { .WriteByte('{') canonAtom(, .Key) .WriteByte(':') canonLiteral(, .Val) .WriteByte('}') } .WriteByte(')') return true } // canonLabelExpr encodes a label-expression tree structurally. func canonLabelExpr( *strings.Builder, *ast.LabelExpr) bool { switch .Kind { case ast.LabelName: .WriteString(":n(") canonAtom(, .Name) .WriteByte(')') case ast.LabelAnd, ast.LabelOr: if .Kind == ast.LabelAnd { .WriteString(":and(") } else { .WriteString(":or(") } if !(, .L) || !(, .R) { return false } .WriteByte(')') case ast.LabelWild: .WriteString(":w") case ast.LabelNot: .WriteString(":not(") if !(, .L) { return false } .WriteByte(')') default: return false } return true } // canonLiteral encodes a literal, parameters by slot/name (sound within // one execution: parameter values are fixed per Ctx). func canonLiteral( *strings.Builder, ast.Literal) { switch .Kind { case ast.LitInt: .WriteByte('i') .WriteString(strconv.FormatInt(.I, 10)) case ast.LitFloat: .WriteByte('f') .WriteString(strconv.FormatFloat(.F, 'b', -1, 64)) case ast.LitStr: .WriteByte('s') .WriteString(strconv.Quote(.S)) case ast.LitBool: .WriteByte('b') if .B { .WriteByte('1') } else { .WriteByte('0') } case ast.LitParam: .WriteByte('p') .WriteString(strconv.FormatUint(uint64(.P), 10)) case ast.LitNamedParam: .WriteString("P(") canonAtom(, .S) .WriteByte(')') default: .WriteByte('n') // null } } // canonExpr encodes the WHERE subset decor-eligible subqueries use; // anything else fails closed. func canonExpr( *strings.Builder, ast.Expr, map[string]string) bool { switch n := .(type) { case *ast.Lit: canonLiteral(, .Value) case *ast.Var: canonVar(, .Name, ) case *ast.Prop: canonVar(, .Var, ) .WriteByte('.') canonAtom(, .Key) case *ast.PropOf: .WriteString("of(") if !(, .Base, ) { return false } .WriteByte('.') canonAtom(, .Key) .WriteByte(')') case *ast.Unary: .WriteByte('u') .WriteByte(byte('0' + int(.Op))) if !(, .Expr, ) { return false } case *ast.Binary: .WriteString("(b") .WriteString(strconv.Itoa(int(.Op))) if !(, .LHS, ) { return false } .WriteByte(',') if !(, .RHS, ) { return false } .WriteByte(')') case *ast.IsNull: .WriteString("isnull(") if .Negated { .WriteByte('!') } if !(, .Expr, ) { return false } .WriteByte(')') case *ast.In: .WriteString("in(") if !(, .Expr, ) { return false } .WriteByte(',') if !(, .List, ) { return false } .WriteByte(')') case *ast.ListExpr: .WriteString("l(") for , := range .Elems { if !(, , ) { return false } .WriteByte(',') } .WriteByte(')') case *ast.Func: .WriteString("fn(") canonAtom(, strings.ToLower(.Name)) if .Distinct || .Star { return false // never in a decor WHERE; fail closed } for , := range .Args { .WriteByte(',') if !(, , ) { return false } } .WriteByte(')') case *ast.Case: .WriteString("case(") if .Operand != nil { if !(, .Operand, ) { return false } } for , := range .Whens { .WriteString(";w") if !(, .Cond, ) { return false } .WriteString(";t") if !(, .Result, ) { return false } } if .Else != nil { .WriteString(";e") if !(, .Else, ) { return false } } .WriteByte(')') case *ast.HasLabelExpr: .WriteString("has(") canonVar(, .Var, ) if !canonLabelExpr(, .Expr) { return false } .WriteByte(')') default: return false } return true }