1
0
mirror of https://github.com/MontFerret/ferret.git synced 2025-08-15 20:02:56 +02:00

Refactor loop projection logic: remove redundant loop parameter from projection functions, optimize group variable handling, and enhance integration tests for COLLECT queries.

This commit is contained in:
Tim Voronov
2025-07-14 12:21:20 -04:00
parent b0d89cc3b6
commit 2827103f1e
3 changed files with 118 additions and 53 deletions

View File

@@ -40,10 +40,8 @@ func (c *LoopCollectCompiler) compileCollect(ctx fql.ICollectClauseContext, aggr
return core.NewKV(vm.NoopOperand, vm.NoopOperand), nil return core.NewKV(vm.NoopOperand, vm.NoopOperand), nil
} }
loop := c.ctx.Loops.Current()
kv, groupSelectors := c.initializeCollector(grouping) kv, groupSelectors := c.initializeCollector(grouping)
projectionVarName, collectorType := c.initializeProjection(ctx, loop, kv, counter, grouping != nil) projectionVarName, collectorType := c.initializeProjection(ctx, kv, counter, grouping != nil)
// 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 aggregation && collectorType != core.CollectorTypeKeyGroup { if aggregation && collectorType != core.CollectorTypeKeyGroup {
@@ -51,6 +49,7 @@ func (c *LoopCollectCompiler) compileCollect(ctx fql.ICollectClauseContext, aggr
collectorType = core.CollectorTypeKeyGroup collectorType = core.CollectorTypeKeyGroup
} }
loop := c.ctx.Loops.Current()
c.finalizeCollector(loop, collectorType, kv) c.finalizeCollector(loop, collectorType, kv)
// If we are using a projection, we need to ensure the loop is set to ForInLoop // If we are using a projection, we need to ensure the loop is set to ForInLoop
@@ -115,13 +114,13 @@ func (c *LoopCollectCompiler) finalizeCollector(loop *core.Loop, collectorType c
// initializeProjection handles the projection setup for group variables and counters. // initializeProjection handles the projection setup for group variables and counters.
// Returns the projection variable name and the appropriate collector type. // Returns the projection variable name and the appropriate collector type.
func (c *LoopCollectCompiler) initializeProjection(ctx fql.ICollectClauseContext, loop *core.Loop, kv *core.KV, counter fql.ICollectCounterContext, hasGrouping bool) (string, core.CollectorType) { func (c *LoopCollectCompiler) initializeProjection(ctx fql.ICollectClauseContext, kv *core.KV, counter fql.ICollectCounterContext, hasGrouping bool) (string, core.CollectorType) {
projectionVariableName := "" projectionVariableName := ""
collectorType := core.CollectorTypeKey collectorType := core.CollectorTypeKey
// Handle group variable projection // Handle group variable projection
if groupVar := ctx.CollectGroupVariable(); groupVar != nil { if groupVar := ctx.CollectGroupVariable(); groupVar != nil {
projectionVariableName = c.compileGroupVariableProjection(loop, kv, groupVar) projectionVariableName = c.compileGroupVariableProjection(kv, groupVar)
collectorType = core.CollectorTypeKeyGroup collectorType = core.CollectorTypeKeyGroup
return projectionVariableName, collectorType return projectionVariableName, collectorType
} }
@@ -176,15 +175,15 @@ func (c *LoopCollectCompiler) compileGrouping(ctx fql.ICollectGroupingContext) (
} }
// compileGroupVariableProjection processes group variable projections (both default and custom). // compileGroupVariableProjection processes group variable projections (both default and custom).
func (c *LoopCollectCompiler) compileGroupVariableProjection(loop *core.Loop, kv *core.KV, groupVar fql.ICollectGroupVariableContext) string { func (c *LoopCollectCompiler) compileGroupVariableProjection(kv *core.KV, groupVar fql.ICollectGroupVariableContext) string {
// Handle default projection (identifier) // Handle default projection (identifier)
if identifier := groupVar.Identifier(); identifier != nil { if identifier := groupVar.Identifier(); identifier != nil {
return c.compileDefaultGroupProjection(loop, kv, identifier, groupVar.CollectGroupVariableKeeper()) return c.compileDefaultGroupProjection(kv, identifier, groupVar.CollectGroupVariableKeeper())
} }
// Handle custom projection (selector expression) // Handle custom projection (selector expression)
if selector := groupVar.CollectSelector(); selector != nil { if selector := groupVar.CollectSelector(); selector != nil {
return c.compileCustomGroupProjection(loop, kv, selector) return c.compileCustomGroupProjection(kv, selector)
} }
return "" return ""
@@ -226,7 +225,7 @@ func (c *LoopCollectCompiler) compileGroupSelectorVariables(selectors []fql.ICol
} }
} }
func (c *LoopCollectCompiler) compileDefaultGroupProjection(loop *core.Loop, kv *core.KV, identifier antlr.TerminalNode, keeper fql.ICollectGroupVariableKeeperContext) string { func (c *LoopCollectCompiler) compileDefaultGroupProjection(kv *core.KV, identifier antlr.TerminalNode, keeper fql.ICollectGroupVariableKeeperContext) string {
if keeper == nil { if keeper == nil {
variables := c.ctx.Symbols.LocalVariables() variables := c.ctx.Symbols.LocalVariables()
scope := core.NewScopeProjection(c.ctx.Registers, c.ctx.Emitter, c.ctx.Symbols, variables) scope := core.NewScopeProjection(c.ctx.Registers, c.ctx.Emitter, c.ctx.Symbols, variables)
@@ -255,7 +254,7 @@ func (c *LoopCollectCompiler) compileDefaultGroupProjection(loop *core.Loop, kv
return identifier.GetText() return identifier.GetText()
} }
func (c *LoopCollectCompiler) compileCustomGroupProjection(_ *core.Loop, kv *core.KV, selector fql.ICollectSelectorContext) string { func (c *LoopCollectCompiler) compileCustomGroupProjection(kv *core.KV, selector fql.ICollectSelectorContext) string {
selectorReg := c.ctx.ExprCompiler.Compile(selector.Expression()) selectorReg := c.ctx.ExprCompiler.Compile(selector.Expression())
c.ctx.Emitter.EmitMove(kv.Value, selectorReg) c.ctx.Emitter.EmitMove(kv.Value, selectorReg)
c.ctx.Registers.Free(selectorReg) c.ctx.Registers.Free(selectorReg)

View File

@@ -513,5 +513,60 @@ FOR u IN users
"uniqueSkillCount": 4, "uniqueSkillCount": 4,
}, },
}, "Should aggregate with array operations"), }, "Should aggregate with array operations"),
CaseArray(`
LET users = [
{
active: true,
married: true,
age: 31,
gender: "m"
},
{
active: true,
married: false,
age: 25,
gender: "f"
},
{
active: true,
married: false,
age: 36,
gender: "m"
},
{
active: false,
married: true,
age: 69,
gender: "m"
},
{
active: true,
married: true,
age: 45,
gender: "f"
}
]
FOR i IN 0..4
LET u = users[i]
COLLECT gender = u.gender
AGGREGATE minAge = MIN(u.age), maxAge = MAX(u.age)
RETURN {
gender: gender,
minAge,
maxAge
}
`, []any{
map[string]any{
"gender": "f",
"maxAge": 45,
"minAge": 25,
},
map[string]any{
"gender": "m",
"maxAge": 69,
"minAge": 31,
},
}),
}) })
} }

View File

@@ -610,7 +610,7 @@ func TestForWhileNested(t *testing.T) {
}, },
}, },
}), }),
SkipCaseArray(` CaseArray(`
LET users = [ LET users = [
{ {
active: true, active: true,
@@ -652,40 +652,45 @@ func TestForWhileNested(t *testing.T) {
gender: CONCAT(gender, n), gender: CONCAT(gender, n),
values: genders values: genders
} }
`, []any{map[string]any{ `, []any{
"gender": "f0",
"values": []map[string]any{
{
"i": map[string]any{
"active": true,
"age": 25,
"gender": "f",
"married": false,
},
},
{
"i": map[string]any{
"active": true,
"age": 45,
"gender": "f",
"married": true,
},
},
},
},
map[string]any{ map[string]any{
"gender": "f1", "gender": "f0",
"values": []map[string]any{ "values": []any{
{ map[string]any{
"i": map[string]any{ "i": 1,
"u": map[string]any{
"active": true, "active": true,
"age": 25, "age": 25,
"gender": "f", "gender": "f",
"married": false, "married": false,
}, },
}, },
{ map[string]any{
"i": map[string]any{ "i": 4,
"u": map[string]any{
"active": true,
"age": 45,
"gender": "f",
"married": true,
},
},
},
},
map[string]any{
"gender": "f1",
"values": []any{
map[string]any{
"i": 1,
"u": map[string]any{
"active": true,
"age": 25,
"gender": "f",
"married": false,
},
},
map[string]any{
"i": 4,
"u": map[string]any{
"active": true, "active": true,
"age": 45, "age": 45,
"gender": "f", "gender": "f",
@@ -696,25 +701,28 @@ func TestForWhileNested(t *testing.T) {
}, },
map[string]any{ map[string]any{
"gender": "m0", "gender": "m0",
"values": []map[string]any{ "values": []any{
{ map[string]any{
"i": map[string]any{ "i": 0,
"u": map[string]any{
"active": true, "active": true,
"age": 31, "age": 31,
"gender": "m", "gender": "m",
"married": true, "married": true,
}, },
}, },
{ map[string]any{
"i": map[string]any{ "i": 2,
"u": map[string]any{
"active": true, "active": true,
"age": 36, "age": 36,
"gender": "m", "gender": "m",
"married": false, "married": false,
}, },
}, },
{ map[string]any{
"i": map[string]any{ "i": 3,
"u": map[string]any{
"active": false, "active": false,
"age": 69, "age": 69,
"gender": "m", "gender": "m",
@@ -725,25 +733,28 @@ func TestForWhileNested(t *testing.T) {
}, },
map[string]any{ map[string]any{
"gender": "m1", "gender": "m1",
"values": []map[string]any{ "values": []any{
{ map[string]any{
"i": map[string]any{ "i": 0,
"u": map[string]any{
"active": true, "active": true,
"age": 31, "age": 31,
"gender": "m", "gender": "m",
"married": true, "married": true,
}, },
}, },
{ map[string]any{
"i": map[string]any{ "i": 2,
"u": map[string]any{
"active": true, "active": true,
"age": 36, "age": 36,
"gender": "m", "gender": "m",
"married": false, "married": false,
}, },
}, },
{ map[string]any{
"i": map[string]any{ "i": 3,
"u": map[string]any{
"active": false, "active": false,
"age": 69, "age": 69,
"gender": "m", "gender": "m",
@@ -753,7 +764,7 @@ func TestForWhileNested(t *testing.T) {
}, },
}, },
}), }),
SkipCaseArray(` CaseArray(`
LET users = [ LET users = [
{ {
active: true, active: true,