// Desugar: query normalization that runs once on the parsed AST before // binding/planning. This is a verbatim copy of gql/internal/semantics/ // desugar.go (which cannot be imported outside the module) with the local // aliases substituted for the ast package. Keep in sync with upstream. // // Lowers non-literal inline pattern property values: a constant value // ((t:Tag {name: 'Hot'}), {id: $p}) stays on the fast props seek path, but // an expression value ((t:Tag {name: tagVar}), {since: a.created}) is // rewritten to a WHERE var.key = value equality conjunct on the (possibly // synthesized) node/relationship variable, so pattern matching never // evaluates an expression inside a pattern. // // Idempotent: a second pass finds the PropExprs already drained.
package gqlparser import ( ) // Desugar normalizes q in place. A synthetic-variable counter threads // through so an anonymous node/relationship carrying a non-literal inline // property gets a stable name to compare on. func ( *Query) error { := 0 return desugarParts(.Parts, &) } func desugarParts( []QueryPart, *int) error { for := range { := &[] for , := range .Clauses { if := desugarClause(, ); != nil { return } } if := desugarProjection(&.Ret, ); != nil { return } } return nil } func desugarClause( Clause, *int) error { switch n := .(type) { case *Match: for := range .Patterns { if := lowerPatternProps(&.Patterns[], &.Where, ); != nil { return } } return desugarExpr(.Where, ) case *With: if := desugarProjection(&.Proj, ); != nil { return } return desugarExpr(.Where, ) case *ShortestPath: if := lowerPatternProps(&.Pattern, &.Where, ); != nil { return } if .Weight != nil && .Weight.Kind == CostExpr { if := desugarExpr(.Weight.Expr, ); != nil { return } } return desugarExpr(.Where, ) case *PathBind: if := lowerPatternProps(&.Pattern, &.Where, ); != nil { return } return desugarExpr(.Where, ) case *CallProc: for , := range .Args { if := desugarExpr(, ); != nil { return } } return nil case *Unwind: return desugarExpr(.Expr, ) case *CallSubquery: return desugarParts(.Query.Parts, ) } return nil } func desugarProjection( *Projection, *int) error { for := range .Items { if := desugarExpr(.Items[].Expr, ); != nil { return } } for := range .OrderBy { if := desugarExpr(.OrderBy[].Expr, ); != nil { return } } return nil } // desugarExpr recurses an expression, lowering inline props inside // EXISTS {} / COUNT {} / pattern-comprehension subpatterns into the // subquery's own WHERE. func desugarExpr( Expr, *int) error { var error ast.Walk(, func( ast.Expr) bool { switch n := .(type) { case *Exists: = lowerPatternProps(.Pattern, &.Where, ) case *CountSub: = lowerPatternProps(.Pattern, &.Where, ) case *PatternComp: = lowerPatternProps(.Pattern, &.Where, ) } return == nil }) return } // lowerPatternProps lowers every non-literal inline property in pattern to // an equality conjunct ANDed into where. func lowerPatternProps( *Pattern, *Expr, *int) error { lowerNodeProps(&.Start, , ) for := range .Hops { if := lowerRelProps(&.Hops[].Rel, , ); != nil { return } lowerNodeProps(&.Hops[].Node, , ) } return nil } func lowerNodeProps( *NodePat, *Expr, *int) { if .LabelExpr != nil { andInto(, &HasLabelExpr{Var: ensureVar(&.Var, ), Expr: .LabelExpr}) .LabelExpr = nil } if .Where != nil { andInto(, .Where) .Where = nil } if len(.PropExprs) == 0 { return } := ensureVar(&.Var, ) for , := range .PropExprs { andInto(, eqConj(, .Key, .Val)) } .PropExprs = nil } func lowerRelProps( *RelPat, *Expr, *int) error { if .Where != nil { if .Length != nil { return desugarPlanErrf("an inline predicate on a variable-length relationship is not supported (Tier 1)") } andInto(, .Where) .Where = nil } if len(.PropExprs) == 0 { return nil } if .Length != nil { return desugarPlanErrf("a non-literal inline property on a variable-length relationship is not supported (Tier 1)") } := ensureVar(&.Var, ) for , := range .PropExprs { andInto(, eqConj(, .Key, .Val)) } .PropExprs = nil return nil } // ensureVar is the variable a lowered equality compares on: the pattern // element's own name, or a fresh __ip{n} for an anonymous element. func ensureVar( *string, *int) string { if * != "" { return * } := fmt.Sprintf("__ip%d", *) *++ * = return } func eqConj(, string, Expr) Expr { return &Binary{ Op: OpEq, LHS: &Prop{Var: , Key: }, RHS: , } } func andInto( *Expr, Expr) { if * == nil { * = return } * = &Binary{Op: OpAnd, LHS: *, RHS: } } // desugarPlanErrf mirrors semantics.planErrf: a planning-restriction error. func desugarPlanErrf( string, ...any) error { return fmt.Errorf(, ...) }