package exec
import (
"slices"
"github.com/freeeve/gochickpeas/gql/internal/ast"
"github.com/freeeve/gochickpeas/gql/internal/eval"
"github.com/freeeve/gochickpeas/gql/internal/explain"
"github.com/freeeve/gochickpeas/gql/internal/graph"
"github.com/freeeve/gochickpeas/gql/internal/plan"
"github.com/freeeve/gochickpeas/gql/value"
)
type terminal interface {
rowSink
finalize() [][]value .Value
}
type stageProfCell struct {
match []uint64
single *uint64
}
var (
identPassthroughs int
disableIdentPassthrough bool
)
func identityBoundary(seg *plan .Segment ) bool {
if len (seg .Stages ) != 0 || seg .Proj .Aggregated || seg .Proj .Distinct {
return false
}
if len (seg .Proj .Returns ) != seg .RowWidth {
return false
}
for i , r := range seg .Proj .Returns {
v , ok := r .Expr .(*ast .Var )
if !ok {
return false
}
if s , bound := seg .Slots [v .Name ]; !bound || s != i {
return false
}
}
return true
}
func runSegment(ctx *eval .Ctx , seg *plan .Segment , inputs [][]value .Value , prof *explain .SegProf ) [][]value .Value {
if seg .ColAgg && prof == nil {
if out , n , ok := tryColumnarAggChain (ctx , []*plan .Segment {seg }, 0 , inputs ); ok && n == 1 {
return out
}
}
if identityBoundary (seg ) && !disableIdentPassthrough &&
(len (inputs ) == 0 || len (inputs [0 ]) == seg .RowWidth ) {
identPassthroughs ++
out := inputs
if len (seg .Proj .OrderBy ) > 0 {
out = sortRowsByOrder (ctx , &seg .Proj , seg .Slots , func (int ) []value .Value { return nil }, 0 , out )
}
out = paginate (out , seg .Proj .Skip , seg .Proj .Limit )
if prof != nil {
prof .ProjRows = uint64 (len (out ))
}
if seg .PostWhere != nil {
if len (seg .Proj .OrderBy ) == 0 {
out = slices .Clone (out )
}
applyPostWhere (ctx , seg , &out )
if prof != nil {
n := uint64 (len (out ))
prof .PostWhereRows = &n
}
}
return out
}
bound := segmentBoundSlots (seg )
var sample []value .Value
if len (inputs ) > 0 {
sample = make ([]value .Value , seg .RowWidth )
copy (sample , inputs [0 ])
}
constIn := func (s int ) bool {
if s < 0 || s >= len (bound ) || bound [s ] {
return false
}
return slotAgrees (s , inputs , true )
}
uniformIn := func (s int ) bool {
return s >= 0 && len (inputs ) > 0 && slotAgrees (s , inputs , true )
}
var term terminal
if seg .Proj .Aggregated {
term = newAggSink (ctx , &seg .Proj , seg .Slots )
} else {
term = newProjSink (ctx , &seg .Proj , seg .Slots , seg .RowWidth )
}
var sink rowSink = term
profCells := make ([]stageProfCell , len (seg .Stages ))
uniq := &uniqEnv {}
for i := len (seg .Stages ) - 1 ; i >= 0 ; i -- {
var cell *stageProfCell
if prof != nil {
cell = &profCells [i ]
}
sink = buildStageSink (ctx , seg , seg .Stages [i ], sink , constIn , uniformIn , sample , cell , uniq )
}
buf := make ([]value .Value , seg .RowWidth )
for _ , in := range inputs {
clear (buf )
copy (buf , in )
if !sink .push (buf ) {
break
}
}
sink .close ()
out := term .finalize ()
if prof != nil {
for _ , c := range profCells {
if c .match != nil {
prof .Stages = append (prof .Stages , explain .StageProf {Match : c .match })
} else {
prof .Stages = append (prof .Stages , explain .StageProf {Single : c .single })
}
}
prof .ProjRows = uint64 (len (out ))
}
applyPostWhere (ctx , seg , &out )
if prof != nil && seg .PostWhere != nil {
n := uint64 (len (out ))
prof .PostWhereRows = &n
}
return out
}
func buildStageSink(ctx *eval .Ctx , seg *plan .Segment , st plan .Stage , next rowSink , constIn , uniformIn func (int ) bool , sample []value .Value , cell *stageProfCell , uniq *uniqEnv ) rowSink {
single := func () *uint64 {
if cell == nil {
return nil
}
cell .single = new (uint64 )
return cell .single
}
switch s := st .(type ) {
case *plan .MatchStage :
ms := &matchSink {
ctx : ctx , stage : s , comp : compileStage (ctx , s , seg .Slots , constIn , uniformIn , sample , seg .Proj .Distinct ),
slots : seg .Slots , buf : make ([]value .Value , seg .RowWidth ), next : next , uniq : uniq ,
}
ms .emitFn = ms .emit
if s .Optional {
ms .orig = make ([]value .Value , seg .RowWidth )
}
if s .PathBind != nil && s .Where != nil {
var conjs []ast .Expr
plan .SplitAnd (s .Where , &conjs )
for _ , c := range conjs {
ms .pathFilters = append (ms .pathFilters , compileEval (ctx , c , seg .Slots ))
}
}
if cell != nil {
ms .opRows = make ([]uint64 , len (s .Ops )+1 )
cell .match = ms .opRows
}
return ms
case *plan .HashJoinStage :
return newHashJoinSink (ctx , seg , s , next , uniq , single ())
case *plan .SpStage :
return &spSink {ctx : ctx , sp : s , arena : rowArena {width : seg .RowWidth }, next : next , count : single ()}
case *plan .GateStage :
gs := &gateSink {
ctx : ctx , gs : s , slots : seg .Slots , arena : rowArena {width : seg .RowWidth },
next : next , count : single (),
where : compileEval (ctx , s .Where , seg .Slots ),
}
for _ , d := range s .Derived {
gs .derived = append (gs .derived , compileEval (ctx , d .Expr , seg .Slots ))
}
return gs
case *plan .CallStage :
cs := &callSink {ctx : ctx , cs : s , buf : make ([]value .Value , seg .RowWidth ), next : next , count : single ()}
if native , ok := ctx .G .(graph .Native ); ok {
cs .native = true
cs .g = native .Snapshot ()
if len (s .ArgExprs ) > 0 {
cs .argEvals = make ([]RowEval , len (s .ArgExprs ))
for i , a := range s .ArgExprs {
cs .argEvals [i ] = compileEval (ctx , a , seg .Slots )
}
cs .slots = seg .Slots
} else {
cs .values , cs .hits , cs .prop = callResults (&s .Proc , cs .g )
}
}
return cs
case *plan .UnwindStage :
return &unwindSink {
ctx : ctx , list : compileEval (ctx , s .List , seg .Slots ), slots : seg .Slots ,
out : s .OutSlot , buf : make ([]value .Value , seg .RowWidth ), next : next , count : single (),
}
case *plan .GroupJoinStage :
return &groupJoinSink {
ctx : ctx , gj : s , buf : make ([]value .Value , seg .RowWidth ),
next : next , count : single (),
}
default :
s2 := st .(*plan .CallSubqueryStage )
return &subquerySink {
ctx : ctx , cs : s2 , buf : make ([]value .Value , seg .RowWidth ),
seed : make ([]value .Value , len (s2 .ImportSlots )), next : next , count : single (),
}
}
}
func segmentBoundSlots(seg *plan .Segment ) []bool {
b := make ([]bool , seg .RowWidth )
set := func (s int ) {
if s >= 0 && s < len (b ) {
b [s ] = true
}
}
for _ , st := range seg .Stages {
switch s := st .(type ) {
case *plan .MatchStage :
for i := range s .Ops {
op := &s .Ops [i ]
set (slotOf (op ))
if op .Kind == plan .OpExpand || op .Kind == plan .OpVarExpand {
set (op .RelSlot )
}
}
if s .PathBind != nil {
set (s .PathBind .PathSlot )
}
case *plan .HashJoinStage :
for _ , p := range s .PayloadSlots {
set (p )
}
case *plan .SpStage :
set (s .PathSlot )
case *plan .GateStage :
set (s .Sp .PathSlot )
for _ , d := range s .Derived {
set (d .Slot )
}
case *plan .CallStage :
set (s .NodeSlot )
set (s .ValueSlot )
case *plan .UnwindStage :
set (s .OutSlot )
case *plan .CallSubqueryStage :
for _ , o := range s .OutSlots {
set (o )
}
case *plan .GroupJoinStage :
for _ , o := range s .OutSlots {
set (o )
}
}
}
return b
}
func streamableBoundary(seg *plan .Segment ) bool {
p := &seg .Proj
return !p .Aggregated && !p .Distinct && len (p .OrderBy ) == 0 &&
p .Skip == nil && p .Limit == nil && len (p .Post ) == 0 && p .NHidden == 0
}
type passthroughSink struct {
ctx *eval .Ctx
returns []RowEval
slots map [string ]int
where RowEval
colScope map [string ]int
out []value .Value
buf []value .Value
next rowSink
}
func newPassthroughSink(ctx *eval .Ctx , seg *plan .Segment , nextWidth int , next rowSink ) *passthroughSink {
p := &passthroughSink {
ctx : ctx ,
returns : make ([]RowEval , len (seg .Proj .Returns )),
slots : seg .Slots ,
out : make ([]value .Value , len (seg .Proj .Returns )),
buf : make ([]value .Value , nextWidth ),
next : next ,
}
for i , r := range seg .Proj .Returns {
p .returns [i ] = compileEval (ctx , r .Expr , seg .Slots )
}
if seg .PostWhere != nil {
p .colScope = make (map [string ]int , len (seg .Proj .Columns ))
for i , c := range seg .Proj .Columns {
p .colScope [c ] = i
}
p .where = compileEval (ctx , seg .PostWhere , p .colScope )
}
return p
}
func (p *passthroughSink ) push (row []value .Value ) bool {
for i , c := range p .returns {
p .out [i ] = c .Eval (p .ctx , row , p .slots )
}
if p .where != nil && !p .where .Eval (p .ctx , p .out , p .colScope ).IsTruthy () {
return true
}
clear (p .buf )
copy (p .buf , p .out )
return p .next .push (p .buf )
}
func (p *passthroughSink ) close () { p .next .close () }
func buildChain(ctx *eval .Ctx , seg *plan .Segment , term rowSink , constIn , uniformIn func (int ) bool , sample []value .Value ) rowSink {
uniq := &uniqEnv {}
sink := term
for i := len (seg .Stages ) - 1 ; i >= 0 ; i -- {
sink = buildStageSink (ctx , seg , seg .Stages [i ], sink , constIn , uniformIn , sample , nil , uniq )
}
return sink
}
func runSegmentRun(ctx *eval .Ctx , segs []*plan .Segment , inputs [][]value .Value ) [][]value .Value {
last := segs [len (segs )-1 ]
if len (segs ) == 1 {
return runSegment (ctx , last , inputs , nil )
}
never := func (int ) bool { return false }
var term terminal
if last .Proj .Aggregated {
term = newAggSink (ctx , &last .Proj , last .Slots )
} else {
term = newProjSink (ctx , &last .Proj , last .Slots , last .RowWidth )
}
head := buildChain (ctx , last , term , never , never , nil )
for k := len (segs ) - 2 ; k >= 0 ; k -- {
seg := segs [k ]
pt := newPassthroughSink (ctx , seg , segs [k +1 ].RowWidth , head )
constIn := never
uniformIn := never
var sample []value .Value
if k == 0 {
bound := segmentBoundSlots (seg )
if len (inputs ) > 0 {
sample = make ([]value .Value , seg .RowWidth )
copy (sample , inputs [0 ])
}
constIn = func (s int ) bool {
if s < 0 || s >= len (bound ) || bound [s ] {
return false
}
return slotAgrees (s , inputs , true )
}
uniformIn = func (s int ) bool {
return s >= 0 && len (inputs ) > 0 && slotAgrees (s , inputs , true )
}
}
head = buildChain (ctx , seg , pt , constIn , uniformIn , sample )
}
buf := make ([]value .Value , segs [0 ].RowWidth )
for _ , in := range inputs {
clear (buf )
copy (buf , in )
if !head .push (buf ) {
break
}
}
head .close ()
out := term .finalize ()
applyPostWhere (ctx , last , &out )
return out
}
func applyPostWhere(ctx *eval .Ctx , seg *plan .Segment , out *[][]value .Value ) {
if seg .PostWhere == nil {
return
}
scope := make (map [string ]int , len (seg .Proj .Columns ))
for i , c := range seg .Proj .Columns {
scope [c ] = i
}
rows := *out
isConst := func (s int ) bool { return slotAgrees (s , rows , false ) }
var sample []value .Value
if len (rows ) > 0 {
sample = rows [0 ]
}
w := hoistEval (ctx , compileEval (ctx , seg .PostWhere , scope ), isConst , func (int ) bool { return false }, sample , scope )
kept := (*out )[:0 ]
for _ , r := range *out {
if w .Eval (ctx , r , scope ).IsTruthy () {
kept = append (kept , r )
}
}
*out = kept
}
The pages are generated with Golds v0.8.7 . (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu .
PR and bug reports are welcome and can be submitted to the issue list .
Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds .