// Whole-row predicate specialization: a comparison whose sides are // property reads over typed i64 columns (optionally shifted by a constant // integer or months-free duration) -- the dominant fully-bound filter // shape, e.g. a temporal ordering between two pattern variables -- runs as // a monomorphic closure reading both columns directly, skipping the // compiled tree's dispatch and per-row boxing. A slot-vs-slot comparison // flattens to one shared-kernel Compare call. Rows whose slots do not // hold nodes fall back to the full tree evaluation, so results are exactly // the general path's for every input.
package compile import ( chickpeas ) // addChecked / mulChecked are checked int64 add / multiply (comma-ok), // mirroring eval's temporal guard so the specialization's constant fold // declines exactly when the interpreter would overflow to Null. func addChecked(, int64) (int64, bool) { := + return , ( > ) == ( > 0) || == 0 } func mulChecked(, int64) (int64, bool) { if == 0 || == 0 { return 0, true } if ( == math.MinInt64 && == -1) || ( == math.MinInt64 && == -1) { return 0, false } := * return , / == } // rowFast is a monomorphic whole-expression evaluator, result-identical // to ceval on the same tree. type rowFast func(ctx *eval.Ctx, row []value.Value, slots map[string]int) value.Value // i64Term reads one comparison side: an i64 node-column property, // optionally shifted by a compile-time constant. type i64Term struct { slot int col chickpeas.I64Col mode uint8 off int64 sub bool } const ( termPlain uint8 = iota // termDur shifts by a months-free duration's tick offset -- Arith's // Int +/- Duration through ApplyDuration, which for months == 0 is // unchecked tick addition (off carries the operator's sign). termDur // termInt shifts by an integer constant with Arith's checked Int // add/subtract semantics (overflow = Null). termInt ) // read resolves the term against a row. state readNull propagates a Null // operand (absent property, null slot, checked overflow) into the // comparison's Null result; readFallback sends the whole row to the tree // evaluation (a slot holding anything besides a node or null). const ( readOK uint8 = iota readNull readFallback ) func ( *i64Term) ( []value.Value) (int64, uint8) { if .slot < 0 || .slot >= len() { return 0, readNull } := [.slot] if .Kind() != value.KindNode { if .IsNull() { return 0, readNull } return 0, readFallback } , := .AsNode() , := .col.Get(uint32()) if ! { return 0, readNull } switch .mode { case termDur: // Months-free tick add, checked like eval's fixed ApplyDuration: // overflow is Null (the interpreter produces the same Null). := + .off if ( > ) == (.off > 0) || .off == 0 { return , readOK } return 0, readNull case termInt: // Mirror eval's checked int arithmetic exactly: overflow is Null. if .sub { := - .off if ( < ) == (.off > 0) || .off == 0 { return , readOK } return 0, readNull } := + .off if ( > ) == (.off > 0) || .off == 0 { return , readOK } return 0, readNull } return , readOK } // deriveRowFast specializes a compiled tree whose root is a comparison // over two supported terms; nil keeps the tree evaluation. func deriveRowFast( cnode, *chickpeas.Snapshot) rowFast { // A bare property read or slot reference -- the dominant projection-item // shapes, evaluated once per output column per row -- skips the tree // dispatch entirely: cevalProp/the slot read are self-contained, and the // closure binds the node directly (ceval's cases do nothing more). if , := .(*cProp); { return func( *eval.Ctx, []value.Value, map[string]int) value.Value { return cevalProp(, ) } } if , := .(*cSlot); { := .s return func( *eval.Ctx, []value.Value, map[string]int) value.Value { return slotVal(, ) } } // A bare prop-vs-const comparison is already fused to cCmpPropConst; derive // the monomorphic i64 compare from it so the common filter shape skips // value.Compare's type switch too (rev marks the const on the left). if , := .(*cCmpPropConst); { if .prop.reader.node.kind != colI64 { return nil } , , := constI64Val(.c) if ! { return nil } := i64Term{slot: .prop.slot, col: .prop.reader.node.i64} return propLitFast(, , , opKeep(.op), .rev, , ) } , := .(*cBin) if ! { return nil } switch .op { case ast.OpEq, ast.OpNeq, ast.OpLt, ast.OpLte, ast.OpGt, ast.OpGte: default: return nil } := opKeep(.op) if , := .l.(*cSlot); { if , := .r.(*cSlot); { , := .s, .s return func( *eval.Ctx, []value.Value, map[string]int) value.Value { , := value.Compare(slotVal(, ), slotVal(, )) if ! { return value.Null() } return value.Bool(()) } } } , := i64TermOf(.l) , := i64TermOf(.r) if && { return func( *eval.Ctx, []value.Value, map[string]int) value.Value { , := .read() if == readFallback { return ceval(, , , , ) } , := .read() if == readFallback { return ceval(, , , , ) } if == readNull || == readNull { return value.Null() } // Same-kind ints compare through float64 exactly like // value.Compare's asNum path -- mirroring is the invariant. , := cmpFloat(float64(), float64()) if ! { return value.Null() } return value.Bool(()) } } // One typed term against a foldable i64 literal -- WHERE p.age > 30 / // WHERE m.creationDate < datetime(...), the most common filter shape, which // the two-term derivation above silently declined. if { if , , := constI64(.r); { return propLitFast(, , , , false, , ) } } if { if , , := constI64(.l); { return propLitFast(, , , , true, , ) } } return nil } // constI64 folds a bare comparison literal to the int64 the monomorphic path // compares against, reporting whether value.Compare orders it EXACTLY (int64) // rather than through the float64 asNum path. Only Int and Temporal literals // fold: an Int compares Int-vs-Int through asNum (exact=false, matching the // two-term path and the always-Int column read); a Temporal compares against // the i64 epoch-millis column through cmpInt (exact=true), so folding it to its // millis is bit-identical. Float/Null/Str/Bool/Duration are declined -- an // integer compare cannot reproduce their semantics (truncating a float would // silently change the result). func constI64( cnode) ( int64, , bool) { , := .(*cLit) if ! { return 0, false, false } return constI64Val(.v) } // constI64Val is constI64's fold over an already-extracted value -- the form // cCmpPropConst needs, whose folded constant is a value.Value rather than a // cLit node. Same Int/Temporal-only rule; see constI64. func constI64Val( value.Value) ( int64, , bool) { if , := .AsInt(); { return , false, true } if , , := .AsTemporal(); { return , true, true } return 0, false, false } // propLitFast is the one-sided fast path: a typed i64 term compared to a folded // constant -- one column read and an integer compare per row. exact selects the // comparison value.Compare uses for the literal's kind (int64 for a Temporal, // float64 asNum for an Int); litOnLeft preserves operand order so // datetime(...) < m.creationDate is not silently inverted. func propLitFast( i64Term, int64, bool, func(int) bool, bool, cnode, *chickpeas.Snapshot) rowFast { return func( *eval.Ctx, []value.Value, map[string]int) value.Value { , := .read() if == readFallback { return ceval(, , , , ) } if == readNull { return value.Null() } , := , if { , = , } var int := true if { = cmpI64(, ) } else { , = cmpFloat(float64(), float64()) } if ! { return value.Null() } return value.Bool(()) } } // cmpI64 is the exact int64 ordering value.Compare uses for temporal operands. func cmpI64(, int64) int { switch { case < : return -1 case > : return 1 } return 0 } // i64TermOf resolves a comparison side: a bare i64-column property read, // or one shifted by a constant integer or months-free duration. Constant // subtraction supports only a right-hand constant (Arith defines no // duration-minus-temporal, and integer const-minus-prop is a different // formula than the term encodes). func i64TermOf( cnode) (i64Term, bool) { switch n := .(type) { case *cProp: if .reader.node.kind != colI64 { return i64Term{}, false } return i64Term{slot: .slot, col: .reader.node.i64}, true case *cBin: if .op != ast.OpAdd && .op != ast.OpSub { return i64Term{}, false } , , := splitPropLit(.l, .r) if == nil || ( && .op == ast.OpSub) || .reader.node.kind != colI64 { return i64Term{}, false } := i64Term{slot: .slot, col: .reader.node.i64} if , , , := .v.AsDuration(); { if != 0 { return i64Term{}, false } // Fold the months-free duration to a tick offset, checked: if the // constant itself overflows, decline the specialization so the // row falls to the tree, whose fixed ApplyDuration yields Null. , := mulChecked(, eval.MSPerDay) if ! { return i64Term{}, false } , := addChecked(, ) if ! { return i64Term{}, false } if .op == ast.OpSub { if == math.MinInt64 { return i64Term{}, false } = - } .mode, .off = termDur, return , true } if , := .v.AsInt(); { .mode, .off, .sub = termInt, , .op == ast.OpSub return , true } } return i64Term{}, false } // splitPropLit splits a binary's operands into (property, literal); rev // marks the property on the right. func splitPropLit(, cnode) ( *cProp, *cLit, bool) { if , := .(*cProp); { if , := .(*cLit); { return , , false } } if , := .(*cProp); { if , := .(*cLit); { return , , true } } return nil, nil, false } // slotVal mirrors cSlot evaluation: out-of-range reads are Null. func slotVal( []value.Value, int) value.Value { if >= 0 && < len() { return [] } return value.Null() }