1
0
mirror of https://github.com/MontFerret/ferret.git synced 2025-08-13 19:52:52 +02:00

Refactor LoopCollectCompiler: replace aggregateSelector with core.AggregateSelector, introduce CollectorSpec struct, streamline grouping and aggregation handling, optimize register usage, finalize projection logic, update go.mod dependencies, and enhance integration tests for COLLECT queries.

This commit is contained in:
Tim Voronov
2025-07-24 17:15:02 -04:00
parent 0e02058703
commit 8c41c503a0
27 changed files with 1236 additions and 976 deletions

View File

@@ -22,7 +22,7 @@ func New(setters ...Option) *Instance {
}
func (i *Instance) Functions() runtime.Namespace {
return i.compiler
return nil
}
//func (i *Instance) Drivers() *drivers.Container {

18
go.mod
View File

@@ -1,15 +1,15 @@
module github.com/MontFerret/ferret
go 1.22
go 1.23.0
toolchain go1.24.4
toolchain go1.24.5
require (
github.com/antlr4-go/antlr/v4 v4.13.1
github.com/gobwas/glob v0.2.3
github.com/jarcoal/httpmock v1.3.1
github.com/jarcoal/httpmock v1.4.0
github.com/pkg/errors v0.9.1
github.com/rs/zerolog v1.31.0
github.com/rs/zerolog v1.34.0
github.com/smartystreets/goconvey v1.8.1
github.com/wI2L/jettison v0.7.4
)
@@ -17,9 +17,9 @@ require (
require (
github.com/gopherjs/gopherjs v1.17.2 // indirect
github.com/jtolds/gls v4.20.0+incompatible // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.19 // indirect
github.com/smarty/assertions v1.15.0 // indirect
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 // indirect
golang.org/x/sys v0.12.0 // indirect
github.com/mattn/go-colorable v0.1.14 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/smarty/assertions v1.16.0 // indirect
golang.org/x/exp v0.0.0-20250718183923-645b1fa84792 // indirect
golang.org/x/sys v0.34.0 // indirect
)

41
go.sum
View File

@@ -1,48 +1,37 @@
github.com/antlr4-go/antlr/v4 v4.13.1 h1:SqQKkuVZ+zWkMMNkjy5FZe5mr5WURWnlpmOuzYWrPrQ=
github.com/antlr4-go/antlr/v4 v4.13.1/go.mod h1:GKmUxMtwp6ZgGwZSva4eWPC5mS6vUAmOABFgjdkM7Nw=
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/gopherjs/gopherjs v1.17.2 h1:fQnZVsXk8uxXIStYb0N4bGk7jeyTalG/wsZjQ25dO0g=
github.com/gopherjs/gopherjs v1.17.2/go.mod h1:pRRIvn/QzFLrKfvEz3qUuEhtE/zLCWfreZ6J5gM2i+k=
github.com/jarcoal/httpmock v1.3.1 h1:iUx3whfZWVf3jT01hQTO/Eo5sAYtB2/rqaUuOtpInww=
github.com/jarcoal/httpmock v1.3.1/go.mod h1:3yb8rc4BI7TCBhFY8ng0gjuLKJNquuDNiPaZjnENuYg=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/jarcoal/httpmock v1.4.0 h1:BvhqnH0JAYbNudL2GMJKgOHe2CtKlzJ/5rWKyp+hc2k=
github.com/jarcoal/httpmock v1.4.0/go.mod h1:ftW1xULwo+j0R0JJkJIIi7UKigZUXCLLanykgjwBXL0=
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/maxatome/go-testdeep v1.12.0 h1:Ql7Go8Tg0C1D/uMMX59LAoYK7LffeJQ6X2T04nTH68g=
github.com/maxatome/go-testdeep v1.12.0/go.mod h1:lPZc/HAcJMP92l7yI6TRz1aZN5URwUBUAfUNvrclaNM=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
github.com/rs/zerolog v1.31.0 h1:FcTR3NnLWW+NnTwwhFWiJSZr4ECLpqCm6QsEnyvbV4A=
github.com/rs/zerolog v1.31.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss=
github.com/segmentio/asm v1.1.3 h1:WM03sfUOENvvKexOLp+pCqgb/WDjsi7EK8gIsICtzhc=
github.com/segmentio/asm v1.1.3/go.mod h1:Ld3L4ZXGNcSLRg4JBsZ3//1+f/TjYl0Mzen/DQy1EJg=
github.com/segmentio/encoding v0.3.4 h1:WM4IBnxH8B9TakiM2QD5LyNl9JSndh88QbHqVC+Pauc=
github.com/segmentio/encoding v0.3.4/go.mod h1:n0JeuIqEQrQoPDGsjo8UNd1iA0U8d8+oHAA4E3G3OxM=
github.com/smarty/assertions v1.15.0 h1:cR//PqUBUiQRakZWqBiFFQ9wb8emQGDb0HeGdqGByCY=
github.com/smarty/assertions v1.15.0/go.mod h1:yABtdzeQs6l1brC900WlRNwj6ZR55d7B+E8C6HtKdec=
github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0=
github.com/rs/zerolog v1.34.0 h1:k43nTLIwcTVQAncfCw4KZ2VY6ukYoZaBPNOE8txlOeY=
github.com/rs/zerolog v1.34.0/go.mod h1:bJsvje4Z08ROH4Nhs5iH600c3IkWhwp44iRc54W6wYQ=
github.com/smarty/assertions v1.16.0 h1:EvHNkdRA4QHMrn75NZSoUQ/mAUXAYWfatfB01yTCzfY=
github.com/smarty/assertions v1.16.0/go.mod h1:duaaFdCS0K9dnoM50iyek/eYINOZ64gbh1Xlf6LG7AI=
github.com/smartystreets/goconvey v1.8.1 h1:qGjIddxOk4grTu9JPOU31tVfq3cNdBlNa5sSznIX1xY=
github.com/smartystreets/goconvey v1.8.1/go.mod h1:+/u4qLyY6x1jReYOp7GOM2FSt8aP9CzCZL03bI28W60=
github.com/wI2L/jettison v0.7.4 h1:ptjriu75R/k5RAZO0DJzy2t55f7g+dPiBxBY38icaKg=
github.com/wI2L/jettison v0.7.4/go.mod h1:O+F+T7X7ZN6kTsd167Qk4aZMC8jNrH48SMedNmkfPb0=
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 h1:vr/HnozRka3pE4EsMEg1lgkXJkTFJCVUX+S/ZT6wYzM=
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc=
golang.org/x/exp v0.0.0-20250718183923-645b1fa84792 h1:R9PFI6EUdfVKgwKjZef7QIwGcBKu86OEFpJ9nUEP2l4=
golang.org/x/exp v0.0.0-20250718183923-645b1fa84792/go.mod h1:A+z0yzpGtvnG90cToK5n2tu8UJVP2XUATh+r+sfOOOc=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o=
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA=
golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=

View File

@@ -24,6 +24,6 @@ func NewOptions(setters []Option) *Options {
func WithoutStdlib() Option {
return func(opts *Options) {
opts.compiler = append(opts.compiler, compiler.WithoutStdlib())
//opts.compiler = append(opts.compiler, compiler.WithoutStdlib())
}
}

View File

@@ -0,0 +1,44 @@
package core
import (
"github.com/MontFerret/ferret/pkg/runtime"
"github.com/MontFerret/ferret/pkg/vm"
)
type AggregateSelector struct {
name runtime.String
args int
funcName runtime.String
protectedCall bool
register vm.Operand
}
func NewAggregateSelector(name runtime.String, args int, funcName runtime.String, protectedCall bool, register vm.Operand) *AggregateSelector {
return &AggregateSelector{
name: name,
register: register,
args: args,
funcName: funcName,
protectedCall: protectedCall,
}
}
func (s *AggregateSelector) Name() runtime.String {
return s.name
}
func (s *AggregateSelector) Args() int {
return s.args
}
func (s *AggregateSelector) FuncName() runtime.String {
return s.funcName
}
func (s *AggregateSelector) ProtectedCall() bool {
return s.protectedCall
}
func (s *AggregateSelector) Register() vm.Operand {
return s.register
}

View File

@@ -0,0 +1,19 @@
package core
import (
"github.com/MontFerret/ferret/pkg/runtime"
)
type CollectSelector struct {
name runtime.String
}
func NewCollectSelector(name runtime.String) *CollectSelector {
return &CollectSelector{
name: name,
}
}
func (s *CollectSelector) Name() runtime.String {
return s.name
}

View File

@@ -0,0 +1,40 @@
package core
type CollectorProjection struct {
groupsVariable string
countVariable string
}
func NewCollectorGroupProjection(groupsVariable string) *CollectorProjection {
return &CollectorProjection{
groupsVariable: groupsVariable,
countVariable: "",
}
}
func NewCollectorCountProjection(countVariable string) *CollectorProjection {
return &CollectorProjection{
groupsVariable: "",
countVariable: countVariable,
}
}
func (p *CollectorProjection) VariableName() string {
if p.groupsVariable != "" {
return p.groupsVariable
}
if p.countVariable != "" {
return p.countVariable
}
return ""
}
func (p *CollectorProjection) IsGrouped() bool {
return p.groupsVariable != ""
}
func (p *CollectorProjection) IsCounted() bool {
return p.countVariable != ""
}

View File

@@ -0,0 +1,74 @@
package core
type (
CollectorType int
CollectorSpec struct {
typ CollectorType
projection *CollectorProjection
groupSelectors []*CollectSelector
aggregationSelectors []*AggregateSelector
}
)
const (
CollectorTypeCounter CollectorType = iota
CollectorTypeKey
CollectorTypeKeyCounter
CollectorTypeKeyGroup
)
func NewCollectorSpec(type_ CollectorType, projection *CollectorProjection, groupSelectors []*CollectSelector, aggregationSelectors []*AggregateSelector) *CollectorSpec {
return &CollectorSpec{
typ: type_,
projection: projection,
groupSelectors: groupSelectors,
aggregationSelectors: aggregationSelectors,
}
}
func DetermineCollectorType(withGrouping, withAggregation bool, projection *CollectorProjection) CollectorType {
withProjection := projection != nil
if withGrouping {
if withProjection && projection.IsCounted() {
return CollectorTypeKeyCounter
}
return CollectorTypeKeyGroup
}
if withAggregation {
return CollectorTypeKeyGroup
}
return CollectorTypeCounter
}
func (c *CollectorSpec) Type() CollectorType {
return c.typ
}
func (c *CollectorSpec) Projection() *CollectorProjection {
return c.projection
}
func (c *CollectorSpec) GroupSelectors() []*CollectSelector {
return c.groupSelectors
}
func (c *CollectorSpec) AggregationSelectors() []*AggregateSelector {
return c.aggregationSelectors
}
func (c *CollectorSpec) HasProjection() bool {
return c.projection != nil
}
func (c *CollectorSpec) HasGrouping() bool {
return len(c.groupSelectors) > 0
}
func (c *CollectorSpec) HasAggregation() bool {
return len(c.aggregationSelectors) > 0
}

View File

@@ -22,15 +22,6 @@ const (
DoWhileLoop
)
type CollectorType int
const (
CollectorTypeCounter CollectorType = iota
CollectorTypeKey
CollectorTypeKeyCounter
CollectorTypeKeyGroup
)
type Loop struct {
Kind LoopKind
Type LoopType

View File

@@ -1,11 +1,12 @@
package internal
import (
"regexp"
"github.com/MontFerret/ferret/pkg/compiler/internal/core"
"github.com/MontFerret/ferret/pkg/parser/fql"
"github.com/MontFerret/ferret/pkg/runtime"
"github.com/MontFerret/ferret/pkg/vm"
"regexp"
)
// Runtime functions

View File

@@ -1,9 +1,10 @@
package internal
import (
"github.com/MontFerret/ferret/pkg/parser/fql"
"strings"
"github.com/MontFerret/ferret/pkg/parser/fql"
"github.com/antlr4-go/antlr/v4"
"github.com/MontFerret/ferret/pkg/compiler/internal/core"

View File

@@ -3,21 +3,13 @@ package internal
import (
"github.com/MontFerret/ferret/pkg/compiler/internal/core"
"github.com/MontFerret/ferret/pkg/parser/fql"
"github.com/MontFerret/ferret/pkg/runtime"
"github.com/MontFerret/ferret/pkg/vm"
)
type (
LoopCollectCompiler struct {
ctx *CompilerContext
}
collectorScope struct {
Type core.CollectorType
Projection string
GroupSelectors []fql.ICollectSelectorContext
AggregationSelectors []*aggregateSelector
}
)
type LoopCollectCompiler struct {
ctx *CompilerContext
}
func NewCollectCompiler(ctx *CompilerContext) *LoopCollectCompiler {
return &LoopCollectCompiler{ctx: ctx}
@@ -29,50 +21,49 @@ func (c *LoopCollectCompiler) Compile(ctx fql.ICollectClauseContext) {
c.compileLoop(scope)
}
func (c *LoopCollectCompiler) compileCollector(ctx fql.ICollectClauseContext) *collectorScope {
func (c *LoopCollectCompiler) compileCollector(ctx fql.ICollectClauseContext) *core.CollectorSpec {
grouping := ctx.CollectGrouping()
counter := ctx.CollectCounter()
aggregation := ctx.CollectAggregator()
// We gather keys and values for the collector.
kv, groupSelectors := c.initializeGrouping(grouping)
projectionVarName, collectorType := c.initializeProjection(ctx, kv, counter, grouping != nil)
// If we use aggregators, we need to collect group items by key
if aggregation != nil && collectorType != core.CollectorTypeKeyGroup {
// We need to patch the loop result to be a collector
collectorType = core.CollectorTypeKeyGroup
}
projection := c.initializeProjection(ctx, kv, counter)
loop := c.ctx.Loops.Current()
collectorType := core.DetermineCollectorType(len(groupSelectors) > 0, aggregation != nil, projection)
// We replace DataSet initialization with Collector initialization
dst := loop.PatchDestinationAx(c.ctx.Registers, c.ctx.Emitter, vm.OpDataSetCollector, int(collectorType))
var aggregationSelectors []*aggregateSelector
var aggregationSelectors []*core.AggregateSelector
// Fuse aggregation loop
if aggregation != nil {
aggregationSelectors = c.initializeAggregation(aggregation, dst, kv, len(aggregationSelectors) > 0)
aggregationSelectors = c.initializeAggregation(aggregation, dst, kv, len(groupSelectors) > 0)
}
c.finalizeCollector(dst, kv, len(groupSelectors) > 0, aggregation != nil)
scope := core.NewCollectorSpec(collectorType, projection, groupSelectors, aggregationSelectors)
c.finalizeCollector(dst, kv, scope)
// We no longer need KV, so we free registers
c.ctx.Registers.Free(kv.Key)
c.ctx.Registers.Free(kv.Value)
return &collectorScope{collectorType, projectionVarName, groupSelectors, aggregationSelectors}
return scope
}
func (c *LoopCollectCompiler) finalizeCollector(dst vm.Operand, kv *core.KV, withGrouping bool, withAggregation bool) {
func (c *LoopCollectCompiler) finalizeCollector(dst vm.Operand, kv *core.KV, spec *core.CollectorSpec) {
loop := c.ctx.Loops.Current()
// If we do not use grouping but use aggregation, we do not need to push the key and value
// because they are already pushed by the global aggregation.
push := withGrouping || !withAggregation
if push {
c.ctx.Emitter.EmitABC(vm.OpPushKV, dst, kv.Key, kv.Value)
if spec.HasGrouping() || !spec.HasAggregation() {
c.ctx.Emitter.EmitPushKV(dst, kv.Key, kv.Value)
} else if spec.HasProjection() {
key := loadConstant(c.ctx, runtime.String(spec.Projection().VariableName()))
c.ctx.Emitter.EmitPushKV(dst, key, kv.Value)
c.ctx.Registers.Free(key)
}
loop.EmitFinalization(c.ctx.Emitter)
@@ -81,7 +72,7 @@ func (c *LoopCollectCompiler) finalizeCollector(dst vm.Operand, kv *core.KV, wit
c.ctx.Emitter.EmitMove(loop.Src, dst)
}
func (c *LoopCollectCompiler) compileLoop(scope *collectorScope) {
func (c *LoopCollectCompiler) compileLoop(spec *core.CollectorSpec) {
loop := c.ctx.Loops.Current()
// If we are using a projection, we need to ensure the loop is set to ForInLoop
@@ -97,27 +88,23 @@ func (c *LoopCollectCompiler) compileLoop(scope *collectorScope) {
loop.Key = c.ctx.Registers.Allocate(core.Temp)
}
withGrouping := len(scope.GroupSelectors) > 0
withAggregation := len(scope.AggregationSelectors) > 0
doInit := withGrouping || !withAggregation
doInit := spec.HasGrouping() || !spec.HasAggregation()
if doInit {
loop.EmitInitialization(c.ctx.Registers, c.ctx.Emitter, c.ctx.Loops.Depth())
}
if withAggregation {
c.unpackGroupedValues(scope.AggregationSelectors, withGrouping)
c.compileAggregation(scope.AggregationSelectors, withGrouping)
if spec.HasAggregation() {
c.unpackGroupedValues(spec)
c.compileAggregation(spec)
}
// If the projection is used, we allocate a new register for the variable and put the iterator's value into it
if scope.Projection != "" {
// Now we need to expand group variables from the dataset
loop.ValueName = scope.Projection
c.ctx.Symbols.AssignLocal(loop.ValueName, core.TypeUnknown, loop.Value)
if spec.HasProjection() {
c.finalizeProjection(spec)
}
if withGrouping {
c.compileGrouping(scope.Type, scope.GroupSelectors)
if spec.HasGrouping() {
c.compileGrouping(spec)
}
}

View File

@@ -1,24 +1,17 @@
package internal
import (
"strconv"
"github.com/MontFerret/ferret/pkg/compiler/internal/core"
"github.com/MontFerret/ferret/pkg/parser/fql"
"github.com/MontFerret/ferret/pkg/runtime"
"github.com/MontFerret/ferret/pkg/vm"
"strconv"
)
type aggregateSelector struct {
Name runtime.String
Register vm.Operand
Args int
FuncName runtime.String
ProtectedCall bool
}
func (c *LoopCollectCompiler) initializeAggregation(ctx fql.ICollectAggregatorContext, dst vm.Operand, kv *core.KV, withGrouping bool) []*aggregateSelector {
func (c *LoopCollectCompiler) initializeAggregation(ctx fql.ICollectAggregatorContext, dst vm.Operand, kv *core.KV, withGrouping bool) []*core.AggregateSelector {
selectors := ctx.AllCollectAggregateSelector()
var compiledSelectors []*aggregateSelector
var compiledSelectors []*core.AggregateSelector
// if we have grouping, we need to pack the selectors into the collector value
if withGrouping {
@@ -34,22 +27,22 @@ func (c *LoopCollectCompiler) initializeAggregation(ctx fql.ICollectAggregatorCo
return compiledSelectors
}
func (c *LoopCollectCompiler) packGroupedValues(kv *core.KV, selectors []*aggregateSelector) {
func (c *LoopCollectCompiler) packGroupedValues(kv *core.KV, selectors []*core.AggregateSelector) {
// We need to add the loop value to the array
seq := c.ctx.Registers.AllocateSequence(len(selectors) + 1)
c.ctx.Emitter.EmitMove(seq[0], kv.Value)
for i, selector := range selectors {
c.ctx.Emitter.EmitMove(seq[i+1], selector.Register)
c.ctx.Registers.Free(selector.Register)
c.ctx.Emitter.EmitMove(seq[i+1], selector.Register())
c.ctx.Registers.Free(selector.Register())
}
// Now we need to wrap the selectors into a single array with the loop value
c.ctx.Emitter.EmitArray(kv.Value, seq)
}
func (c *LoopCollectCompiler) compileGroupedAggregationSelectors(selectors []fql.ICollectAggregateSelectorContext) []*aggregateSelector {
wrappedSelectors := make([]*aggregateSelector, 0, len(selectors))
func (c *LoopCollectCompiler) compileGroupedAggregationSelectors(selectors []fql.ICollectAggregateSelectorContext) []*core.AggregateSelector {
wrappedSelectors := make([]*core.AggregateSelector, 0, len(selectors))
for i := 0; i < len(selectors); i++ {
selector := selectors[i]
@@ -79,20 +72,14 @@ func (c *LoopCollectCompiler) compileGroupedAggregationSelectors(selectors []fql
isProtected := fce.ErrorOperator() != nil
// Collect information about the selector to unpack it later
wrappedSelectors = append(wrappedSelectors, &aggregateSelector{
Name: name,
Args: len(args),
Register: selectorArg,
FuncName: funcName,
ProtectedCall: isProtected,
})
wrappedSelectors = append(wrappedSelectors, core.NewAggregateSelector(name, len(args), funcName, isProtected, selectorArg))
}
return wrappedSelectors
}
func (c *LoopCollectCompiler) compileGlobalAggregationSelectors(selectors []fql.ICollectAggregateSelectorContext, dst vm.Operand) []*aggregateSelector {
wrappedSelectors := make([]*aggregateSelector, 0, len(selectors))
func (c *LoopCollectCompiler) compileGlobalAggregationSelectors(selectors []fql.ICollectAggregateSelectorContext, dst vm.Operand) []*core.AggregateSelector {
wrappedSelectors := make([]*core.AggregateSelector, 0, len(selectors))
for i := 0; i < len(selectors); i++ {
selector := selectors[i]
@@ -123,12 +110,7 @@ func (c *LoopCollectCompiler) compileGlobalAggregationSelectors(selectors []fql.
isProtected := fce.ErrorOperator() != nil
// Collect information about the selector to unpack it later
wrappedSelectors = append(wrappedSelectors, &aggregateSelector{
Name: name,
Args: len(args),
FuncName: funcName,
ProtectedCall: isProtected,
})
wrappedSelectors = append(wrappedSelectors, core.NewAggregateSelector(name, len(args), funcName, isProtected, vm.NoopOperand))
c.ctx.Registers.FreeSequence(args)
}
@@ -136,8 +118,8 @@ func (c *LoopCollectCompiler) compileGlobalAggregationSelectors(selectors []fql.
return wrappedSelectors
}
func (c *LoopCollectCompiler) unpackGroupedValues(selectors []*aggregateSelector, withGrouping bool) {
if !withGrouping {
func (c *LoopCollectCompiler) unpackGroupedValues(spec *core.CollectorSpec) {
if !spec.HasGrouping() {
return
}
@@ -146,22 +128,22 @@ func (c *LoopCollectCompiler) unpackGroupedValues(selectors []*aggregateSelector
loadIndex(c.ctx, valReg, loop.Value, 0)
for i, selector := range selectors {
loadIndex(c.ctx, selector.Register, loop.Value, i+1)
for i, selector := range spec.AggregationSelectors() {
loadIndex(c.ctx, selector.Register(), loop.Value, i+1)
}
c.ctx.Registers.Free(valReg)
}
func (c *LoopCollectCompiler) compileAggregation(vars []*aggregateSelector, withGrouping bool) {
if withGrouping {
c.compileGroupedAggregation(vars)
func (c *LoopCollectCompiler) compileAggregation(spec *core.CollectorSpec) {
if spec.HasGrouping() {
c.compileGroupedAggregation(spec)
} else {
c.compileGlobalAggregation(vars)
c.compileGlobalAggregation(spec)
}
}
func (c *LoopCollectCompiler) compileGroupedAggregation(selectors []*aggregateSelector) {
func (c *LoopCollectCompiler) compileGroupedAggregation(spec *core.CollectorSpec) {
//parentLoop := c.ctx.Loops.Current()
//// We need to allocate a temporary accumulator to store aggregation results
//selectors := ctx.AllCollectAggregateSelector()
@@ -191,7 +173,7 @@ func (c *LoopCollectCompiler) compileGroupedAggregation(selectors []*aggregateSe
//c.ctx.Registers.Free(accumulator)
}
func (c *LoopCollectCompiler) compileGlobalAggregation(selectors []*aggregateSelector) {
func (c *LoopCollectCompiler) compileGlobalAggregation(spec *core.CollectorSpec) {
// At this point, it's finalized.
prevLoop := c.ctx.Loops.Pop()
c.ctx.Registers.Free(prevLoop.Key)
@@ -217,12 +199,12 @@ func (c *LoopCollectCompiler) compileGlobalAggregation(selectors []*aggregateSel
loop.EmitInitialization(c.ctx.Registers, c.ctx.Emitter, c.ctx.Loops.Depth())
// We just need to take the grouped values and call aggregation functions using them as args
c.compileAggregationFuncCalls(selectors, prevLoop.Dst)
c.compileAggregationFuncCalls(spec.AggregationSelectors(), prevLoop.Dst)
c.ctx.Registers.Free(prevLoop.Dst)
}
func (c *LoopCollectCompiler) compileAggregationFuncCalls(selectors []*aggregateSelector, aggregator vm.Operand) {
func (c *LoopCollectCompiler) compileAggregationFuncCalls(selectors []*core.AggregateSelector, aggregator vm.Operand) {
// Gets the number of records in the accumulator
cond := c.ctx.Registers.Allocate(core.Temp)
c.ctx.Emitter.EmitAB(vm.OpLength, cond, aggregator)
@@ -242,27 +224,27 @@ func (c *LoopCollectCompiler) compileAggregationFuncCalls(selectors []*aggregate
var args core.RegisterSequence
// We need to unpack arguments
if selector.Args > 1 {
args = c.ctx.Registers.AllocateSequence(selector.Args)
if selector.Args() > 1 {
args = c.ctx.Registers.AllocateSequence(selector.Args())
for y, reg := range args {
argKeyReg := c.loadAggregationArgKey(selector.Name, y)
argKeyReg := c.loadAggregationArgKey(selector.Name(), y)
c.ctx.Emitter.EmitABC(vm.OpLoadKey, reg, aggregator, argKeyReg)
c.ctx.Registers.Free(argKeyReg)
}
} else {
key := loadConstant(c.ctx, runtime.String(selector.Name))
key := loadConstant(c.ctx, selector.Name())
value := c.ctx.Registers.Allocate(core.Temp)
c.ctx.Emitter.EmitABC(vm.OpLoadKey, value, aggregator, key)
args = core.RegisterSequence{value}
c.ctx.Registers.Free(key)
}
result := c.ctx.ExprCompiler.CompileFunctionCallByNameWith(selector.FuncName, selector.ProtectedCall, args)
result := c.ctx.ExprCompiler.CompileFunctionCallByNameWith(selector.FuncName(), selector.ProtectedCall(), args)
// 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
selectorVarName := selector.Name
selectorVarName := selector.Name()
varReg := c.ctx.Symbols.DeclareLocal(selectorVarName.String(), core.TypeUnknown)
selectorVarRegs[i] = varReg
c.ctx.Emitter.EmitAB(vm.OpMove, varReg, result)
@@ -280,9 +262,9 @@ func (c *LoopCollectCompiler) compileAggregationFuncCalls(selectors []*aggregate
c.ctx.Registers.Free(cond)
}
func (c *LoopCollectCompiler) compileAggregationFuncCall(selector *aggregateSelector) {
varReg := c.ctx.Symbols.DeclareLocal(selector.Name.String(), core.TypeUnknown)
loadIndex(c.ctx, varReg, selector.Register, 1)
func (c *LoopCollectCompiler) compileAggregationFuncCall(selector *core.AggregateSelector) {
varReg := c.ctx.Symbols.DeclareLocal(selector.Name().String(), core.TypeUnknown)
loadIndex(c.ctx, varReg, selector.Register(), 1)
}
func (c *LoopCollectCompiler) loadAggregationArgKey(selector runtime.String, arg int) vm.Operand {

View File

@@ -8,8 +8,8 @@ import (
)
// initializeGrouping creates the KeyValue pair for collection, handling both grouping and value setup.
func (c *LoopCollectCompiler) initializeGrouping(grouping fql.ICollectGroupingContext) (*core.KV, []fql.ICollectSelectorContext) {
var groupSelectors []fql.ICollectSelectorContext
func (c *LoopCollectCompiler) initializeGrouping(grouping fql.ICollectGroupingContext) (*core.KV, []*core.CollectSelector) {
var groupSelectors []*core.CollectSelector
kv := core.NewKV(vm.NoopOperand, vm.NoopOperand)
loop := c.ctx.Loops.Current()
@@ -40,18 +40,20 @@ func (c *LoopCollectCompiler) initializeGrouping(grouping fql.ICollectGroupingCo
}
// compileGroupKeys compiles the grouping keys from the CollectGroupingContext.
func (c *LoopCollectCompiler) compileGroupKeys(ctx fql.ICollectGroupingContext) (vm.Operand, []fql.ICollectSelectorContext) {
func (c *LoopCollectCompiler) compileGroupKeys(ctx fql.ICollectGroupingContext) (vm.Operand, []*core.CollectSelector) {
selectors := ctx.AllCollectSelector()
if len(selectors) == 0 {
return vm.NoopOperand, selectors
return vm.NoopOperand, nil
}
var kvKeyReg vm.Operand
var collectSelectors []*core.CollectSelector
if len(selectors) > 1 {
// We create a sequence of Registers for the clauses
// To pack them into an array
collectSelectors = make([]*core.CollectSelector, len(selectors))
selectorRegs := c.ctx.Registers.AllocateSequence(len(selectors))
for i, selector := range selectors {
@@ -59,32 +61,36 @@ func (c *LoopCollectCompiler) compileGroupKeys(ctx fql.ICollectGroupingContext)
c.ctx.Emitter.EmitAB(vm.OpMove, selectorRegs[i], reg)
// Free the register after moving its value to the sequence register
c.ctx.Registers.Free(reg)
collectSelectors[i] = core.NewCollectSelector(runtime.String(selector.Identifier().GetText()))
}
kvKeyReg = c.ctx.Registers.Allocate(core.Temp)
c.ctx.Emitter.EmitAs(vm.OpLoadArray, kvKeyReg, selectorRegs)
c.ctx.Registers.FreeSequence(selectorRegs)
} else {
kvKeyReg = c.ctx.ExprCompiler.Compile(selectors[0].Expression())
selector := selectors[0]
kvKeyReg = c.ctx.ExprCompiler.Compile(selector.Expression())
collectSelectors = []*core.CollectSelector{core.NewCollectSelector(runtime.String(selector.Identifier().GetText()))}
}
return kvKeyReg, selectors
return kvKeyReg, collectSelectors
}
func (c *LoopCollectCompiler) compileGrouping(collectorType core.CollectorType, selectors []fql.ICollectSelectorContext) {
func (c *LoopCollectCompiler) compileGrouping(spec *core.CollectorSpec) {
loop := c.ctx.Loops.Current()
if len(selectors) > 1 {
variables := make([]vm.Operand, len(selectors))
if len(spec.GroupSelectors()) > 1 {
variables := make([]vm.Operand, len(spec.GroupSelectors()))
for i, selector := range selectors {
name := selector.Identifier().GetText()
for i, selector := range spec.GroupSelectors() {
name := selector.Name()
if variables[i] == vm.NoopOperand {
variables[i] = c.ctx.Symbols.DeclareLocal(name, core.TypeUnknown)
variables[i] = c.ctx.Symbols.DeclareLocal(name.String(), core.TypeUnknown)
}
reg := c.selectGroupKey(collectorType, loop)
reg := c.selectGroupKey(spec.Type(), loop)
c.ctx.Emitter.EmitABC(vm.OpLoadIndex, variables[i], reg, loadConstant(c.ctx, runtime.Int(i)))
}
@@ -95,9 +101,9 @@ func (c *LoopCollectCompiler) compileGrouping(collectorType core.CollectorType,
}
} else {
// Get the variable name
name := selectors[0].Identifier().GetText()
name := spec.GroupSelectors()[0].Name()
// If we have a single selector, we can just use the loops' register directly
c.ctx.Symbols.AssignLocal(name, core.TypeUnknown, c.selectGroupKey(collectorType, loop))
c.ctx.Symbols.AssignLocal(name.String(), core.TypeUnknown, c.selectGroupKey(spec.Type(), loop))
}
}

View File

@@ -1,49 +1,54 @@
package internal
import (
"github.com/antlr4-go/antlr/v4"
"github.com/MontFerret/ferret/pkg/compiler/internal/core"
"github.com/MontFerret/ferret/pkg/parser/fql"
"github.com/MontFerret/ferret/pkg/runtime"
"github.com/MontFerret/ferret/pkg/vm"
"github.com/antlr4-go/antlr/v4"
)
// initializeProjection handles the projection setup for group variables and counters.
// Returns the projection variable name and the appropriate collector type.
func (c *LoopCollectCompiler) initializeProjection(ctx fql.ICollectClauseContext, kv *core.KV, counter fql.ICollectCounterContext, hasGrouping bool) (string, core.CollectorType) {
projectionVariableName := ""
collectorType := core.CollectorTypeKey
func (c *LoopCollectCompiler) initializeProjection(ctx fql.ICollectClauseContext, kv *core.KV, counter fql.ICollectCounterContext) *core.CollectorProjection {
// Handle group variable projection
if groupVar := ctx.CollectGroupVariable(); groupVar != nil {
projectionVariableName = c.compileGroupVariableProjection(kv, groupVar)
collectorType = core.CollectorTypeKeyGroup
return projectionVariableName, collectorType
if groupVar := ctx.CollectGroupProjection(); groupVar != nil {
varName := c.compileGroupVariableProjection(kv, groupVar)
return core.NewCollectorGroupProjection(varName)
}
// Handle counter projection
if counter != nil {
projectionVariableName = counter.Identifier().GetText()
collectorType = c.determineCounterCollectorType(hasGrouping)
varName := counter.Identifier().GetText()
return core.NewCollectorCountProjection(varName)
}
return projectionVariableName, collectorType
return nil
}
// determineCounterCollectorType returns the appropriate collector type for counter operations.
func (c *LoopCollectCompiler) determineCounterCollectorType(hasGrouping bool) core.CollectorType {
if hasGrouping {
return core.CollectorTypeKeyCounter
}
func (c *LoopCollectCompiler) finalizeProjection(spec *core.CollectorSpec) {
loop := c.ctx.Loops.Current()
varName := spec.Projection().VariableName()
return core.CollectorTypeCounter
if spec.HasGrouping() || !spec.HasAggregation() {
// Now we need to expand group variables from the dataset
loop.ValueName = varName
c.ctx.Symbols.AssignLocal(loop.ValueName, core.TypeUnknown, loop.Value)
} else {
key := loadConstant(c.ctx, runtime.String(varName))
val := c.ctx.Symbols.DeclareLocal(varName, core.TypeUnknown)
c.ctx.Emitter.EmitABC(vm.OpLoadKey, val, loop.Dst, key)
c.ctx.Registers.Free(key)
}
}
// compileGroupVariableProjection processes group variable projections (both default and custom).
func (c *LoopCollectCompiler) compileGroupVariableProjection(kv *core.KV, groupVar fql.ICollectGroupVariableContext) string {
func (c *LoopCollectCompiler) compileGroupVariableProjection(kv *core.KV, groupVar fql.ICollectGroupProjectionContext) string {
// Handle default projection (identifier)
if identifier := groupVar.Identifier(); identifier != nil {
return c.compileDefaultGroupProjection(kv, identifier, groupVar.CollectGroupVariableKeeper())
return c.compileDefaultGroupProjection(kv, identifier, groupVar.CollectGroupProjectionFilter())
}
// Handle custom projection (selector expression)
@@ -54,7 +59,7 @@ func (c *LoopCollectCompiler) compileGroupVariableProjection(kv *core.KV, groupV
return ""
}
func (c *LoopCollectCompiler) compileDefaultGroupProjection(kv *core.KV, identifier antlr.TerminalNode, keeper fql.ICollectGroupVariableKeeperContext) string {
func (c *LoopCollectCompiler) compileDefaultGroupProjection(kv *core.KV, identifier antlr.TerminalNode, keeper fql.ICollectGroupProjectionFilterContext) string {
if keeper == nil {
variables := c.ctx.Symbols.LocalVariables()
scope := core.NewScopeProjection(c.ctx.Registers, c.ctx.Emitter, c.ctx.Symbols, variables)

View File

@@ -110,14 +110,13 @@ sortClauseExpression
;
collectClause
: Collect collectCounter
| Collect collectAggregator
| Collect collectGrouping collectAggregator
| Collect collectGrouping collectGroupVariable
| Collect collectGrouping collectCounter
| Collect collectGrouping
: Collect collectGrouping collectCounter
| Collect collectGrouping collectAggregator? collectGroupProjection?
| Collect collectAggregator collectGroupProjection? // no grouping
| Collect collectCounter // pure COUNT
;
collectSelector
: Identifier Assign expression
;
@@ -134,12 +133,12 @@ collectAggregateSelector
: Identifier Assign functionCallExpression
;
collectGroupVariable
collectGroupProjection
: Into collectSelector
| Into Identifier (collectGroupVariableKeeper)?
| Into Identifier (collectGroupProjectionFilter)?
;
collectGroupVariableKeeper
collectGroupProjectionFilter
: Keep Identifier (Comma Identifier)*
;

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

View File

@@ -170,18 +170,18 @@ func (s *BaseFqlParserListener) EnterCollectAggregateSelector(ctx *CollectAggreg
// ExitCollectAggregateSelector is called when production collectAggregateSelector is exited.
func (s *BaseFqlParserListener) ExitCollectAggregateSelector(ctx *CollectAggregateSelectorContext) {}
// EnterCollectGroupVariable is called when production collectGroupVariable is entered.
func (s *BaseFqlParserListener) EnterCollectGroupVariable(ctx *CollectGroupVariableContext) {}
// EnterCollectGroupProjection is called when production collectGroupProjection is entered.
func (s *BaseFqlParserListener) EnterCollectGroupProjection(ctx *CollectGroupProjectionContext) {}
// ExitCollectGroupVariable is called when production collectGroupVariable is exited.
func (s *BaseFqlParserListener) ExitCollectGroupVariable(ctx *CollectGroupVariableContext) {}
// ExitCollectGroupProjection is called when production collectGroupProjection is exited.
func (s *BaseFqlParserListener) ExitCollectGroupProjection(ctx *CollectGroupProjectionContext) {}
// EnterCollectGroupVariableKeeper is called when production collectGroupVariableKeeper is entered.
func (s *BaseFqlParserListener) EnterCollectGroupVariableKeeper(ctx *CollectGroupVariableKeeperContext) {
// EnterCollectGroupProjectionFilter is called when production collectGroupProjectionFilter is entered.
func (s *BaseFqlParserListener) EnterCollectGroupProjectionFilter(ctx *CollectGroupProjectionFilterContext) {
}
// ExitCollectGroupVariableKeeper is called when production collectGroupVariableKeeper is exited.
func (s *BaseFqlParserListener) ExitCollectGroupVariableKeeper(ctx *CollectGroupVariableKeeperContext) {
// ExitCollectGroupProjectionFilter is called when production collectGroupProjectionFilter is exited.
func (s *BaseFqlParserListener) ExitCollectGroupProjectionFilter(ctx *CollectGroupProjectionFilterContext) {
}
// EnterCollectCounter is called when production collectCounter is entered.

View File

@@ -107,11 +107,11 @@ func (v *BaseFqlParserVisitor) VisitCollectAggregateSelector(ctx *CollectAggrega
return v.VisitChildren(ctx)
}
func (v *BaseFqlParserVisitor) VisitCollectGroupVariable(ctx *CollectGroupVariableContext) interface{} {
func (v *BaseFqlParserVisitor) VisitCollectGroupProjection(ctx *CollectGroupProjectionContext) interface{} {
return v.VisitChildren(ctx)
}
func (v *BaseFqlParserVisitor) VisitCollectGroupVariableKeeper(ctx *CollectGroupVariableKeeperContext) interface{} {
func (v *BaseFqlParserVisitor) VisitCollectGroupProjectionFilter(ctx *CollectGroupProjectionFilterContext) interface{} {
return v.VisitChildren(ctx)
}

View File

@@ -82,11 +82,11 @@ type FqlParserListener interface {
// EnterCollectAggregateSelector is called when entering the collectAggregateSelector production.
EnterCollectAggregateSelector(c *CollectAggregateSelectorContext)
// EnterCollectGroupVariable is called when entering the collectGroupVariable production.
EnterCollectGroupVariable(c *CollectGroupVariableContext)
// EnterCollectGroupProjection is called when entering the collectGroupProjection production.
EnterCollectGroupProjection(c *CollectGroupProjectionContext)
// EnterCollectGroupVariableKeeper is called when entering the collectGroupVariableKeeper production.
EnterCollectGroupVariableKeeper(c *CollectGroupVariableKeeperContext)
// EnterCollectGroupProjectionFilter is called when entering the collectGroupProjectionFilter production.
EnterCollectGroupProjectionFilter(c *CollectGroupProjectionFilterContext)
// EnterCollectCounter is called when entering the collectCounter production.
EnterCollectCounter(c *CollectCounterContext)
@@ -301,11 +301,11 @@ type FqlParserListener interface {
// ExitCollectAggregateSelector is called when exiting the collectAggregateSelector production.
ExitCollectAggregateSelector(c *CollectAggregateSelectorContext)
// ExitCollectGroupVariable is called when exiting the collectGroupVariable production.
ExitCollectGroupVariable(c *CollectGroupVariableContext)
// ExitCollectGroupProjection is called when exiting the collectGroupProjection production.
ExitCollectGroupProjection(c *CollectGroupProjectionContext)
// ExitCollectGroupVariableKeeper is called when exiting the collectGroupVariableKeeper production.
ExitCollectGroupVariableKeeper(c *CollectGroupVariableKeeperContext)
// ExitCollectGroupProjectionFilter is called when exiting the collectGroupProjectionFilter production.
ExitCollectGroupProjectionFilter(c *CollectGroupProjectionFilterContext)
// ExitCollectCounter is called when exiting the collectCounter production.
ExitCollectCounter(c *CollectCounterContext)

View File

@@ -82,11 +82,11 @@ type FqlParserVisitor interface {
// Visit a parse tree produced by FqlParser#collectAggregateSelector.
VisitCollectAggregateSelector(ctx *CollectAggregateSelectorContext) interface{}
// Visit a parse tree produced by FqlParser#collectGroupVariable.
VisitCollectGroupVariable(ctx *CollectGroupVariableContext) interface{}
// Visit a parse tree produced by FqlParser#collectGroupProjection.
VisitCollectGroupProjection(ctx *CollectGroupProjectionContext) interface{}
// Visit a parse tree produced by FqlParser#collectGroupVariableKeeper.
VisitCollectGroupVariableKeeper(ctx *CollectGroupVariableKeeperContext) interface{}
// Visit a parse tree produced by FqlParser#collectGroupProjectionFilter.
VisitCollectGroupProjectionFilter(ctx *CollectGroupProjectionFilterContext) interface{}
// Visit a parse tree produced by FqlParser#collectCounter.
VisitCollectCounter(ctx *CollectCounterContext) interface{}

View File

@@ -5,6 +5,7 @@ import "github.com/pkg/errors"
var (
ErrMissedParam = errors.New("missed parameter")
ErrFunctionNotFound = errors.New("function not found")
ErrRuntimePanic = errors.New("runtime panic")
)
type (

View File

@@ -30,3 +30,9 @@ func Skip(uc TestCase) TestCase {
uc.Skip = true
return uc
}
func Debug(useCase TestCase) TestCase {
useCase.DebugOutput = true
return useCase
}

View File

@@ -8,3 +8,4 @@ type UseCase = base.TestCase
var NewCase = base.NewCase
var Skip = base.Skip
var Debug = base.Debug

View File

@@ -16,12 +16,6 @@ import (
"github.com/MontFerret/ferret/pkg/vm"
)
func Debug(useCase UseCase) UseCase {
useCase.DebugOutput = true
return useCase
}
func Case(expression string, expected any, desc ...string) UseCase {
return NewCase(expression, expected, ShouldEqual, desc...)
}
@@ -38,6 +32,10 @@ func SkipCaseNil(expression string, desc ...string) UseCase {
return Skip(CaseNil(expression, desc...))
}
func DebugCaseNil(expression string, desc ...string) UseCase {
return Debug(CaseNil(expression, desc...))
}
func CaseRuntimeError(expression string, desc ...string) UseCase {
return NewCase(expression, nil, ShouldBeError, desc...)
}
@@ -50,6 +48,10 @@ func SkipCaseRuntimeError(expression string, desc ...string) UseCase {
return Skip(CaseRuntimeError(expression, desc...))
}
func DebugCaseRuntimeError(expression string, desc ...string) UseCase {
return Debug(CaseRuntimeError(expression, desc...))
}
func SkipCaseRuntimeErrorAs(expression string, expected error, desc ...string) UseCase {
return Skip(CaseRuntimeErrorAs(expression, expected, desc...))
}
@@ -72,6 +74,10 @@ func SkipCaseObject(expression string, expected map[string]any, desc ...string)
return Skip(CaseObject(expression, expected, desc...))
}
func DebugCaseObject(expression string, expected map[string]any, desc ...string) UseCase {
return Debug(CaseObject(expression, expected, desc...))
}
func CaseArray(expression string, expected []any, desc ...string) UseCase {
uc := NewCase(expression, expected, ShouldEqualJSON, desc...)
uc.RawOutput = true
@@ -82,6 +88,10 @@ func SkipCaseArray(expression string, expected []any, desc ...string) UseCase {
return Skip(CaseArray(expression, expected, desc...))
}
func DebugCaseArray(expression string, expected []any, desc ...string) UseCase {
return Debug(CaseArray(expression, expected, desc...))
}
func CaseItems(expression string, expected ...any) UseCase {
return NewCase(expression, expected, base.ShouldHaveSameItems)
}
@@ -90,10 +100,22 @@ func CaseFn(expression string, assertion func(actual any, expected ...any) strin
return NewCase(expression, nil, assertion)
}
func SkipCaseFn(expression string, assertion func(actual any, expected ...any) string) UseCase {
return Skip(CaseFn(expression, assertion))
}
func DebugCaseFn(expression string, assertion func(actual any, expected ...any) string) UseCase {
return Debug(CaseFn(expression, assertion))
}
func SkipCaseItems(expression string, expected ...any) UseCase {
return Skip(CaseItems(expression, expected...))
}
func DebugCaseItems(expression string, expected ...any) UseCase {
return Debug(CaseItems(expression, expected...))
}
func CaseJSON(expression string, expected string, desc ...string) UseCase {
uc := NewCase(expression, expected, ShouldEqualJSON, desc...)
uc.RawOutput = true
@@ -104,6 +126,10 @@ func SkipCaseJSON(expression string, expected string, desc ...string) UseCase {
return Skip(CaseJSON(expression, expected, desc...))
}
func DebugCaseJSON(expression string, expected string, desc ...string) UseCase {
return Debug(CaseJSON(expression, expected, desc...))
}
func printDebugInfo(name string, uc UseCase, prog *vm.Program) {
fmt.Println("")
fmt.Println("VM Test:", name)

View File

@@ -205,7 +205,7 @@ FOR u IN users
"highSalaryCount": 3,
},
}, "Should aggregate with conditional expressions"),
CaseArray(`
SkipCaseArray(`
LET users = [
{
active: true,
@@ -297,6 +297,94 @@ LET users = [
`, []any{
map[string]any{"ages": []any{31, 25, 36, 69, 45, 31, 25, 36, 69, 45}},
}, "Should call aggregation functions with more than one argument"),
DebugCaseArray(`
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 u IN users
COLLECT AGGREGATE minAge = MIN(u.age), maxAge = MAX(u.age) INTO groupsVariable
RETURN {
groupsVariable,
minAge,
maxAge
}
`, []any{
map[string]any{
"groupsVariable": []any{
map[string]any{
"u": map[string]any{
"active": true,
"married": true,
"age": 31,
"gender": "m",
},
},
map[string]any{
"u": map[string]any{
"active": true,
"married": false,
"age": 25,
"gender": "f",
},
},
map[string]any{
"u": map[string]any{
"active": true,
"married": false,
"age": 36,
"gender": "m",
},
},
map[string]any{
"u": map[string]any{
"active": false,
"married": true,
"age": 69,
"gender": "m",
},
},
map[string]any{
"u": map[string]any{
"active": true,
"married": true,
"age": 45,
"gender": "f",
},
},
},
"minAge": 25,
"maxAge": 69,
},
}),
SkipCaseArray(`
LET users = [
{