mirror of
https://github.com/MontFerret/ferret.git
synced 2025-08-15 20:02:56 +02:00
wip
This commit is contained in:
@@ -407,18 +407,23 @@ func (v *Visitor) VisitCollectClause(ctx *fql.CollectClauseContext) interface{}
|
|||||||
// And wrap each loop element by a KeyValuePair
|
// And wrap each loop element by a KeyValuePair
|
||||||
// Where a key is either a single value or a list of values
|
// Where a key is either a single value or a list of values
|
||||||
// These KeyValuePairs are then added to the dataset
|
// These KeyValuePairs are then added to the dataset
|
||||||
var kvKeyReg vm.Operand
|
var kvKeyReg, kvValReg vm.Operand
|
||||||
var groupSelectors []fql.ICollectSelectorContext
|
var groupSelectors []fql.ICollectSelectorContext
|
||||||
var isGrouping bool
|
var isGrouping bool
|
||||||
grouping := ctx.CollectGrouping()
|
grouping := ctx.CollectGrouping()
|
||||||
|
counter := ctx.CollectCounter()
|
||||||
|
aggregator := ctx.CollectAggregator()
|
||||||
|
|
||||||
|
isCollecting := grouping != nil || counter != nil
|
||||||
|
|
||||||
|
if isCollecting {
|
||||||
if grouping != nil {
|
if grouping != nil {
|
||||||
isGrouping = true
|
isGrouping = true
|
||||||
groupSelectors = grouping.AllCollectSelector()
|
groupSelectors = grouping.AllCollectSelector()
|
||||||
kvKeyReg = v.emitCollectGroupKeySelectors(groupSelectors)
|
kvKeyReg = v.emitCollectGroupKeySelectors(groupSelectors)
|
||||||
}
|
}
|
||||||
|
|
||||||
kvValReg := v.Registers.Allocate(Temp)
|
kvValReg = v.Registers.Allocate(Temp)
|
||||||
v.emitIterValue(loop, kvValReg)
|
v.emitIterValue(loop, kvValReg)
|
||||||
|
|
||||||
var projectionVariableName string
|
var projectionVariableName string
|
||||||
@@ -434,8 +439,8 @@ func (v *Visitor) VisitCollectClause(ctx *fql.CollectClauseContext) interface{}
|
|||||||
}
|
}
|
||||||
|
|
||||||
collectorType = CollectorTypeKeyGroup
|
collectorType = CollectorTypeKeyGroup
|
||||||
} else if countVar := ctx.CollectCounter(); countVar != nil {
|
} else if counter != nil {
|
||||||
projectionVariableName = v.emitCollectCountProjection(loop, kvValReg, countVar)
|
projectionVariableName = v.emitCollectCountProjection(loop, kvValReg, counter)
|
||||||
|
|
||||||
if isGrouping {
|
if isGrouping {
|
||||||
collectorType = CollectorTypeKeyCounter
|
collectorType = CollectorTypeKeyCounter
|
||||||
@@ -444,10 +449,8 @@ func (v *Visitor) VisitCollectClause(ctx *fql.CollectClauseContext) interface{}
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
aggregateCtx := ctx.CollectAggregator()
|
|
||||||
|
|
||||||
// If we use aggregators, we need to collect group items by key
|
// If we use aggregators, we need to collect group items by key
|
||||||
if aggregateCtx != nil && collectorType != CollectorTypeKeyGroup {
|
if aggregator != nil && collectorType != CollectorTypeKeyGroup {
|
||||||
// We need to patch the loop result to be a collector
|
// We need to patch the loop result to be a collector
|
||||||
collectorType = CollectorTypeKeyGroup
|
collectorType = CollectorTypeKeyGroup
|
||||||
}
|
}
|
||||||
@@ -458,7 +461,7 @@ func (v *Visitor) VisitCollectClause(ctx *fql.CollectClauseContext) interface{}
|
|||||||
v.emitIterJumpOrClose(loop)
|
v.emitIterJumpOrClose(loop)
|
||||||
|
|
||||||
// Replace the source with the collector
|
// Replace the source with the collector
|
||||||
v.patchSwitchLoop(loop)
|
v.emitPatchLoop(loop)
|
||||||
|
|
||||||
// If the projection is used, we allocate a new register for the variable and put the iterator's value into it
|
// If the projection is used, we allocate a new register for the variable and put the iterator's value into it
|
||||||
if projectionVariableName != "" {
|
if projectionVariableName != "" {
|
||||||
@@ -469,10 +472,11 @@ func (v *Visitor) VisitCollectClause(ctx *fql.CollectClauseContext) interface{}
|
|||||||
v.emitIterKey(loop, kvKeyReg)
|
v.emitIterKey(loop, kvKeyReg)
|
||||||
v.emitIterValue(loop, kvValReg)
|
v.emitIterValue(loop, kvValReg)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Aggregation loop
|
// Aggregation loop
|
||||||
if aggregateCtx != nil {
|
if aggregator != nil {
|
||||||
v.emitCollectAggregator(aggregateCtx, loop)
|
v.emitCollectAggregator(aggregator, loop, isCollecting)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Reuse the Registers
|
// TODO: Reuse the Registers
|
||||||
@@ -481,18 +485,23 @@ func (v *Visitor) VisitCollectClause(ctx *fql.CollectClauseContext) interface{}
|
|||||||
loop.Value = vm.NoopOperand
|
loop.Value = vm.NoopOperand
|
||||||
loop.Key = vm.NoopOperand
|
loop.Key = vm.NoopOperand
|
||||||
|
|
||||||
if isGrouping {
|
if isCollecting && isGrouping {
|
||||||
// Now we are defining new variables for the group selectors
|
// Now we are defining new variables for the group selectors
|
||||||
v.emitCollectGroupKeySelectorVariables(groupSelectors, kvKeyReg, kvValReg, aggregateCtx != nil)
|
v.emitCollectGroupKeySelectorVariables(groupSelectors, kvKeyReg, kvValReg, aggregator != nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *Visitor) emitCollectAggregator(c fql.ICollectAggregatorContext, parentLoop *Loop) {
|
func (v *Visitor) emitCollectAggregator(c fql.ICollectAggregatorContext, parentLoop *Loop, isCollected bool) {
|
||||||
// First of all, we allocate registers for accumulators
|
var accums []vm.Operand
|
||||||
|
var loop *Loop
|
||||||
selectors := c.AllCollectAggregateSelector()
|
selectors := c.AllCollectAggregateSelector()
|
||||||
accums := make([]vm.Operand, len(selectors))
|
|
||||||
|
// If data is collected, we need to allocate a temporary accumulators to store aggregation results
|
||||||
|
if isCollected {
|
||||||
|
// First of all, we allocate registers for accumulators
|
||||||
|
accums = make([]vm.Operand, len(selectors))
|
||||||
|
|
||||||
// We need to allocate a register for each accumulator
|
// We need to allocate a register for each accumulator
|
||||||
for i := 0; i < len(selectors); i++ {
|
for i := 0; i < len(selectors); i++ {
|
||||||
@@ -502,21 +511,27 @@ func (v *Visitor) emitCollectAggregator(c fql.ICollectAggregatorContext, parentL
|
|||||||
v.Emitter.EmitA(vm.OpList, reg)
|
v.Emitter.EmitA(vm.OpList, reg)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Store upper scope for aggregators
|
loop = v.Loops.EnterLoop(TemporalLoop, ForLoop, false)
|
||||||
mainScope := v.Symbols.Scope()
|
|
||||||
// Nested scope for aggregators
|
|
||||||
v.Symbols.EnterScope()
|
|
||||||
loop := v.Loops.EnterLoop(TemporalLoop, ForLoop, false)
|
|
||||||
|
|
||||||
// Now we iterate over the grouped items
|
// Now we iterate over the grouped items
|
||||||
v.emitIterValue(parentLoop, loop.Iterator)
|
v.emitIterValue(parentLoop, loop.Iterator)
|
||||||
|
|
||||||
// We just re-use the same register
|
// We just re-use the same register
|
||||||
v.Emitter.EmitAB(vm.OpIter, loop.Iterator, loop.Iterator)
|
v.Emitter.EmitAB(vm.OpIter, loop.Iterator, loop.Iterator)
|
||||||
// jumpPlaceholder is a placeholder for the exit aggrIterJump position
|
// jumpPlaceholder is a placeholder for the exit aggrIterJump position
|
||||||
loop.Jump = v.Emitter.EmitJumpc(vm.OpIterNext, jumpPlaceholder, loop.Iterator)
|
loop.Jump = v.Emitter.EmitJumpc(vm.OpIterNext, jumpPlaceholder, loop.Iterator)
|
||||||
|
loop.ValueName = parentLoop.ValueName
|
||||||
|
} else {
|
||||||
|
loop = parentLoop
|
||||||
|
// Otherwise, we create a custom collector for aggregators
|
||||||
|
v.Emitter.PatchSwapAx(loop.ResultPos, vm.OpDataSetCollector, loop.Result, int(CollectorTypeKeyGroup))
|
||||||
|
}
|
||||||
|
|
||||||
aggrIterVal := v.Symbols.DefineVariable(parentLoop.ValueName)
|
// Store upper scope for aggregators
|
||||||
|
mainScope := v.Symbols.Scope()
|
||||||
|
// Nested scope for aggregators
|
||||||
|
v.Symbols.EnterScope()
|
||||||
|
|
||||||
|
aggrIterVal := v.Symbols.DefineVariable(loop.ValueName)
|
||||||
v.Emitter.EmitAB(vm.OpIterValue, aggrIterVal, loop.Iterator)
|
v.Emitter.EmitAB(vm.OpIterValue, aggrIterVal, loop.Iterator)
|
||||||
|
|
||||||
// Now we add value selectors to the accumulators
|
// Now we add value selectors to the accumulators
|
||||||
@@ -536,7 +551,16 @@ func (v *Visitor) emitCollectAggregator(c fql.ICollectAggregatorContext, parentL
|
|||||||
}
|
}
|
||||||
|
|
||||||
resultReg := args[0].Accept(v).(vm.Operand)
|
resultReg := args[0].Accept(v).(vm.Operand)
|
||||||
|
|
||||||
|
if isCollected {
|
||||||
v.Emitter.EmitAB(vm.OpPush, accums[i], resultReg)
|
v.Emitter.EmitAB(vm.OpPush, accums[i], resultReg)
|
||||||
|
} else {
|
||||||
|
aggrKeyName := selector.Identifier().GetText()
|
||||||
|
aggrKeyReg := v.loadConstant(runtime.String(aggrKeyName))
|
||||||
|
v.Emitter.EmitABC(vm.OpPushKV, loop.Result, aggrKeyReg, resultReg)
|
||||||
|
v.Registers.Free(aggrKeyReg)
|
||||||
|
}
|
||||||
|
|
||||||
v.Registers.Free(resultReg)
|
v.Registers.Free(resultReg)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -545,6 +569,7 @@ func (v *Visitor) emitCollectAggregator(c fql.ICollectAggregatorContext, parentL
|
|||||||
|
|
||||||
// Now we can iterate over the selectors and execute the aggregation functions by passing the accumulators
|
// Now we can iterate over the selectors and execute the aggregation functions by passing the accumulators
|
||||||
// And define variables for each accumulator result
|
// And define variables for each accumulator result
|
||||||
|
if isCollected {
|
||||||
for i, selector := range selectors {
|
for i, selector := range selectors {
|
||||||
fcx := selector.FunctionCallExpression()
|
fcx := selector.FunctionCallExpression()
|
||||||
// We won't make any checks here, as we already did it before
|
// We won't make any checks here, as we already did it before
|
||||||
@@ -564,6 +589,53 @@ func (v *Visitor) emitCollectAggregator(c fql.ICollectAggregatorContext, parentL
|
|||||||
v.Loops.ExitLoop()
|
v.Loops.ExitLoop()
|
||||||
// Now close the aggregators scope
|
// Now close the aggregators scope
|
||||||
v.Symbols.ExitScope()
|
v.Symbols.ExitScope()
|
||||||
|
} else {
|
||||||
|
// Now close the aggregators scope
|
||||||
|
v.Symbols.ExitScope()
|
||||||
|
|
||||||
|
parentLoop.ValueName = ""
|
||||||
|
parentLoop.KeyName = ""
|
||||||
|
|
||||||
|
// Since we we in the middle of the loop, we need to patch the loop result
|
||||||
|
// Now we just create a range with 1 item to push the aggregated values to the dataset
|
||||||
|
// Replace source with sorted array
|
||||||
|
zero := v.loadConstant(runtime.Int(0))
|
||||||
|
one := v.loadConstant(runtime.Int(1))
|
||||||
|
aggregator := v.Registers.Allocate(Temp)
|
||||||
|
v.Emitter.EmitAB(vm.OpMove, aggregator, loop.Result)
|
||||||
|
v.Symbols.ExitScope()
|
||||||
|
|
||||||
|
v.Symbols.EnterScope()
|
||||||
|
|
||||||
|
// Create new for loop
|
||||||
|
v.Emitter.EmitABC(vm.OpRange, loop.Src, zero, one)
|
||||||
|
v.Emitter.EmitAb(vm.OpDataSet, loop.Result, loop.Distinct)
|
||||||
|
|
||||||
|
// In case of non-collected aggregators, we just iterate over the grouped items
|
||||||
|
// Retrieve the grouped values by key, execute aggregation funcs and assign variable names to the results
|
||||||
|
for _, selector := range selectors {
|
||||||
|
fcx := selector.FunctionCallExpression()
|
||||||
|
// We won't make any checks here, as we already did it before
|
||||||
|
selectorVarName := selector.Identifier().GetText()
|
||||||
|
|
||||||
|
// We execute the function call with the accumulator as an argument
|
||||||
|
key := v.loadConstant(runtime.String(selectorVarName))
|
||||||
|
value := v.Registers.Allocate(Temp)
|
||||||
|
v.Emitter.EmitABC(vm.OpLoadKey, value, aggregator, key)
|
||||||
|
|
||||||
|
result := v.emitFunctionCall(fcx.FunctionCall(), fcx.ErrorOperator() != nil, NewRegisterSequence(value))
|
||||||
|
|
||||||
|
// We define the variable for the selector result in the upper scope
|
||||||
|
// Since this temporary scope is only for aggregators and will be closed after the aggregation
|
||||||
|
varReg := v.Symbols.DefineVariableInScope(selectorVarName, mainScope)
|
||||||
|
v.Emitter.EmitAB(vm.OpMove, varReg, result)
|
||||||
|
v.Registers.Free(result)
|
||||||
|
v.Registers.Free(value)
|
||||||
|
v.Registers.Free(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
v.Registers.Free(aggregator)
|
||||||
|
}
|
||||||
|
|
||||||
// Free the registers for accumulators
|
// Free the registers for accumulators
|
||||||
for _, reg := range accums {
|
for _, reg := range accums {
|
||||||
@@ -1600,8 +1672,8 @@ func (v *Visitor) emitIterJumpOrClose(loop *Loop) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// patchSwitchLoop replaces the source of the loop with a modified dataset
|
// emitPatchLoop replaces the source of the loop with a modified dataset
|
||||||
func (v *Visitor) patchSwitchLoop(loop *Loop) {
|
func (v *Visitor) emitPatchLoop(loop *Loop) {
|
||||||
// Replace source with sorted array
|
// Replace source with sorted array
|
||||||
v.Emitter.EmitAB(vm.OpMove, loop.Src, loop.Result)
|
v.Emitter.EmitAB(vm.OpMove, loop.Src, loop.Result)
|
||||||
|
|
||||||
|
@@ -21,6 +21,10 @@ func NewKeyGroupCollector() Transformer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *KeyGroupCollector) Get(_ context.Context, key runtime.Value) (runtime.Value, error) {
|
||||||
|
return c.grouping[key.String()], nil
|
||||||
|
}
|
||||||
|
|
||||||
func (c *KeyGroupCollector) Iterate(ctx context.Context) (runtime.Iterator, error) {
|
func (c *KeyGroupCollector) Iterate(ctx context.Context) (runtime.Iterator, error) {
|
||||||
if !c.sorted {
|
if !c.sorted {
|
||||||
if err := c.sort(ctx); err != nil {
|
if err := c.sort(ctx); err != nil {
|
||||||
@@ -39,23 +43,6 @@ func (c *KeyGroupCollector) Iterate(ctx context.Context) (runtime.Iterator, erro
|
|||||||
return NewKVIterator(iter), nil
|
return NewKVIterator(iter), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *KeyGroupCollector) sort(ctx context.Context) error {
|
|
||||||
return runtime.SortListWith(ctx, c.Value, func(first, second runtime.Value) int64 {
|
|
||||||
firstKV, firstOk := first.(*KV)
|
|
||||||
secondKV, secondOk := second.(*KV)
|
|
||||||
|
|
||||||
var comp int64
|
|
||||||
|
|
||||||
if firstOk && secondOk {
|
|
||||||
comp = runtime.CompareValues(firstKV.Key, secondKV.Key)
|
|
||||||
} else {
|
|
||||||
comp = runtime.CompareValues(first, second)
|
|
||||||
}
|
|
||||||
|
|
||||||
return comp
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *KeyGroupCollector) Add(ctx context.Context, key, value runtime.Value) error {
|
func (c *KeyGroupCollector) Add(ctx context.Context, key, value runtime.Value) error {
|
||||||
k, err := Stringify(ctx, key)
|
k, err := Stringify(ctx, key)
|
||||||
|
|
||||||
@@ -79,3 +66,20 @@ func (c *KeyGroupCollector) Add(ctx context.Context, key, value runtime.Value) e
|
|||||||
|
|
||||||
return group.Add(ctx, value)
|
return group.Add(ctx, value)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *KeyGroupCollector) sort(ctx context.Context) error {
|
||||||
|
return runtime.SortListWith(ctx, c.Value, func(first, second runtime.Value) int64 {
|
||||||
|
firstKV, firstOk := first.(*KV)
|
||||||
|
secondKV, secondOk := second.(*KV)
|
||||||
|
|
||||||
|
var comp int64
|
||||||
|
|
||||||
|
if firstOk && secondOk {
|
||||||
|
comp = runtime.CompareValues(firstKV.Key, secondKV.Key)
|
||||||
|
} else {
|
||||||
|
comp = runtime.CompareValues(first, second)
|
||||||
|
}
|
||||||
|
|
||||||
|
return comp
|
||||||
|
})
|
||||||
|
}
|
||||||
|
@@ -1,34 +0,0 @@
|
|||||||
package internal
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"github.com/MontFerret/ferret/pkg/runtime"
|
|
||||||
)
|
|
||||||
|
|
||||||
type ValueCollector struct {
|
|
||||||
*runtime.Box[runtime.List]
|
|
||||||
sorted bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewValueCollector() Transformer {
|
|
||||||
return &ValueCollector{
|
|
||||||
Box: &runtime.Box[runtime.List]{
|
|
||||||
Value: runtime.NewArray(16),
|
|
||||||
}}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ValueCollector) Iterate(ctx context.Context) (runtime.Iterator, error) {
|
|
||||||
if !c.sorted {
|
|
||||||
if err := runtime.SortAsc(ctx, c.Value); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
c.sorted = true
|
|
||||||
}
|
|
||||||
|
|
||||||
return c.Value.Iterate(ctx)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ValueCollector) Add(ctx context.Context, _, value runtime.Value) error {
|
|
||||||
return c.Value.Add(ctx, value)
|
|
||||||
}
|
|
@@ -23,7 +23,7 @@ import (
|
|||||||
// Aside from COLLECTs sophisticated grouping and aggregation capabilities, it allows you to place a LIMIT operation before RETURN to potentially stop the COLLECT operation early.
|
// Aside from COLLECTs sophisticated grouping and aggregation capabilities, it allows you to place a LIMIT operation before RETURN to potentially stop the COLLECT operation early.
|
||||||
func TestCollect(t *testing.T) {
|
func TestCollect(t *testing.T) {
|
||||||
RunUseCases(t, []UseCase{
|
RunUseCases(t, []UseCase{
|
||||||
SkipCaseCompilationError(`
|
CaseCompilationError(`
|
||||||
LET users = [
|
LET users = [
|
||||||
{
|
{
|
||||||
active: true,
|
active: true,
|
||||||
@@ -63,7 +63,7 @@ func TestCollect(t *testing.T) {
|
|||||||
gender: gender
|
gender: gender
|
||||||
}
|
}
|
||||||
`, "Should not have access to initial variables"),
|
`, "Should not have access to initial variables"),
|
||||||
SkipCaseCompilationError(`
|
CaseCompilationError(`
|
||||||
LET users = [
|
LET users = [
|
||||||
{
|
{
|
||||||
active: true,
|
active: true,
|
||||||
@@ -101,7 +101,7 @@ func TestCollect(t *testing.T) {
|
|||||||
COLLECT gender = i.gender
|
COLLECT gender = i.gender
|
||||||
RETURN {x, gender}
|
RETURN {x, gender}
|
||||||
`, "Should not have access to variables defined before COLLECT"),
|
`, "Should not have access to variables defined before COLLECT"),
|
||||||
SkipCaseArray(`
|
CaseArray(`
|
||||||
LET users = [
|
LET users = [
|
||||||
{
|
{
|
||||||
active: true,
|
active: true,
|
||||||
@@ -138,7 +138,7 @@ LET users = [
|
|||||||
COLLECT gender = i.gender
|
COLLECT gender = i.gender
|
||||||
RETURN gender
|
RETURN gender
|
||||||
`, []any{"f", "m"}, "Should group result by a single key"),
|
`, []any{"f", "m"}, "Should group result by a single key"),
|
||||||
SkipCaseArray(`
|
CaseArray(`
|
||||||
LET users = [
|
LET users = [
|
||||||
{
|
{
|
||||||
active: true,
|
active: true,
|
||||||
@@ -181,7 +181,7 @@ LET users = [
|
|||||||
map[string]int{"ageGroup": 9},
|
map[string]int{"ageGroup": 9},
|
||||||
map[string]int{"ageGroup": 13},
|
map[string]int{"ageGroup": 13},
|
||||||
}, "Should group result by a single key expression"),
|
}, "Should group result by a single key expression"),
|
||||||
SkipCase(`
|
Case(`
|
||||||
LET users = [
|
LET users = [
|
||||||
{
|
{
|
||||||
active: true,
|
active: true,
|
||||||
@@ -219,7 +219,7 @@ LET users = [
|
|||||||
RETURN gender)
|
RETURN gender)
|
||||||
RETURN grouped[0]
|
RETURN grouped[0]
|
||||||
`, "f", "Should return correct group key by an index"),
|
`, "f", "Should return correct group key by an index"),
|
||||||
SkipCaseArray(
|
CaseArray(
|
||||||
`LET users = [
|
`LET users = [
|
||||||
{
|
{
|
||||||
active: true,
|
active: true,
|
||||||
@@ -262,7 +262,7 @@ LET users = [
|
|||||||
map[string]any{"age": 36, "gender": "m"},
|
map[string]any{"age": 36, "gender": "m"},
|
||||||
map[string]any{"age": 69, "gender": "m"},
|
map[string]any{"age": 69, "gender": "m"},
|
||||||
}, "Should group result by multiple keys"),
|
}, "Should group result by multiple keys"),
|
||||||
SkipCaseArray(`
|
CaseArray(`
|
||||||
LET users = [
|
LET users = [
|
||||||
{
|
{
|
||||||
active: true,
|
active: true,
|
||||||
@@ -353,7 +353,7 @@ LET users = [
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
}, "Should create default projection"),
|
}, "Should create default projection"),
|
||||||
SkipCaseArray(`
|
CaseArray(`
|
||||||
LET users = []
|
LET users = []
|
||||||
FOR i IN users
|
FOR i IN users
|
||||||
COLLECT gender = i.gender INTO genders
|
COLLECT gender = i.gender INTO genders
|
||||||
@@ -362,7 +362,7 @@ LET users = [
|
|||||||
values: genders
|
values: genders
|
||||||
}
|
}
|
||||||
`, []any{}, "COLLECT gender = i.gender INTO genders: should return an empty array when source is empty"),
|
`, []any{}, "COLLECT gender = i.gender INTO genders: should return an empty array when source is empty"),
|
||||||
SkipCaseArray(
|
CaseArray(
|
||||||
`LET users = [
|
`LET users = [
|
||||||
{
|
{
|
||||||
active: true,
|
active: true,
|
||||||
@@ -418,7 +418,7 @@ LET users = [
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
}, "Should create custom projection"),
|
}, "Should create custom projection"),
|
||||||
SkipCaseArray(
|
CaseArray(
|
||||||
`LET users = [
|
`LET users = [
|
||||||
{
|
{
|
||||||
active: true,
|
active: true,
|
||||||
@@ -495,7 +495,7 @@ LET users = [
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
}, "Should create custom projection grouped by multiple keys"),
|
}, "Should create custom projection grouped by multiple keys"),
|
||||||
SkipCaseArray(`
|
CaseArray(`
|
||||||
LET users = [
|
LET users = [
|
||||||
{
|
{
|
||||||
active: true,
|
active: true,
|
||||||
@@ -552,7 +552,7 @@ LET users = [
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
}, "Should create default projection with default KEEP"),
|
}, "Should create default projection with default KEEP"),
|
||||||
SkipCaseArray(`
|
CaseArray(`
|
||||||
LET users = []
|
LET users = []
|
||||||
FOR i IN users
|
FOR i IN users
|
||||||
LET married = i.married
|
LET married = i.married
|
||||||
@@ -562,7 +562,7 @@ LET users = [
|
|||||||
values: genders
|
values: genders
|
||||||
}
|
}
|
||||||
`, []any{}, "COLLECT gender = i.gender INTO genders KEEP married: Should return an empty array when source is empty"),
|
`, []any{}, "COLLECT gender = i.gender INTO genders KEEP married: Should return an empty array when source is empty"),
|
||||||
SkipCaseArray(`
|
CaseArray(`
|
||||||
LET users = [
|
LET users = [
|
||||||
{
|
{
|
||||||
active: true,
|
active: true,
|
||||||
@@ -635,7 +635,7 @@ LET users = [
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
}, "Should create default projection with default KEEP using multiple keys"),
|
}, "Should create default projection with default KEEP using multiple keys"),
|
||||||
SkipCaseArray(`
|
CaseArray(`
|
||||||
LET users = [
|
LET users = [
|
||||||
{
|
{
|
||||||
active: true,
|
active: true,
|
||||||
@@ -692,7 +692,7 @@ LET users = [
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
}, "Should create default projection with custom KEEP"),
|
}, "Should create default projection with custom KEEP"),
|
||||||
SkipCaseArray(`
|
CaseArray(`
|
||||||
LET users = [
|
LET users = [
|
||||||
{
|
{
|
||||||
active: true,
|
active: true,
|
||||||
@@ -765,7 +765,7 @@ LET users = [
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
}, "Should create default projection with custom KEEP using multiple keys"),
|
}, "Should create default projection with custom KEEP using multiple keys"),
|
||||||
SkipCaseArray(`
|
CaseArray(`
|
||||||
LET users = [
|
LET users = [
|
||||||
{
|
{
|
||||||
active: true,
|
active: true,
|
||||||
@@ -822,7 +822,7 @@ LET users = [
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
}, "Should create default projection with custom KEEP with custom name"),
|
}, "Should create default projection with custom KEEP with custom name"),
|
||||||
SkipCaseArray(`
|
CaseArray(`
|
||||||
LET users = [
|
LET users = [
|
||||||
{
|
{
|
||||||
active: true,
|
active: true,
|
||||||
@@ -880,7 +880,7 @@ LET users = [
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
}, "Should create default projection with custom KEEP with multiple custom names"),
|
}, "Should create default projection with custom KEEP with multiple custom names"),
|
||||||
SkipCaseArray(
|
CaseArray(
|
||||||
`LET users = [
|
`LET users = [
|
||||||
{
|
{
|
||||||
active: true,
|
active: true,
|
||||||
@@ -930,7 +930,7 @@ LET users = [
|
|||||||
},
|
},
|
||||||
}, "Should group and count result by a single key"),
|
}, "Should group and count result by a single key"),
|
||||||
|
|
||||||
SkipCaseArray(
|
CaseArray(
|
||||||
`
|
`
|
||||||
LET users = []
|
LET users = []
|
||||||
FOR i IN users
|
FOR i IN users
|
||||||
@@ -940,7 +940,7 @@ LET users = [
|
|||||||
values: numberOfUsers
|
values: numberOfUsers
|
||||||
}
|
}
|
||||||
`, []any{}, "COLLECT gender = i.gender WITH COUNT INTO numberOfUsers: Should return empty array when source is empty"),
|
`, []any{}, "COLLECT gender = i.gender WITH COUNT INTO numberOfUsers: Should return empty array when source is empty"),
|
||||||
SkipCaseArray(
|
CaseArray(
|
||||||
`LET users = [
|
`LET users = [
|
||||||
{
|
{
|
||||||
active: true,
|
active: true,
|
||||||
@@ -979,7 +979,7 @@ LET users = [
|
|||||||
`, []any{
|
`, []any{
|
||||||
5,
|
5,
|
||||||
}, "Should just count the number of items in the source"),
|
}, "Should just count the number of items in the source"),
|
||||||
SkipCaseArray(
|
CaseArray(
|
||||||
`LET users = []
|
`LET users = []
|
||||||
FOR i IN users
|
FOR i IN users
|
||||||
COLLECT WITH COUNT INTO numberOfUsers
|
COLLECT WITH COUNT INTO numberOfUsers
|
||||||
|
Reference in New Issue
Block a user