mirror of
https://github.com/MontFerret/ferret.git
synced 2025-01-18 03:22:02 +02:00
parent
b02c554214
commit
549b4abd3b
309
pkg/compiler/compiler_collect_aggregate_test.go
Normal file
309
pkg/compiler/compiler_collect_aggregate_test.go
Normal file
@ -0,0 +1,309 @@
|
|||||||
|
package compiler_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"github.com/MontFerret/ferret/pkg/compiler"
|
||||||
|
"github.com/MontFerret/ferret/pkg/runtime"
|
||||||
|
. "github.com/smartystreets/goconvey/convey"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestAggregate(t *testing.T) {
|
||||||
|
Convey("Should aggregate values without grouping", t, func() {
|
||||||
|
c := compiler.New()
|
||||||
|
|
||||||
|
prog, err := c.Compile(`
|
||||||
|
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)
|
||||||
|
RETURN {
|
||||||
|
minAge,
|
||||||
|
maxAge
|
||||||
|
}
|
||||||
|
`)
|
||||||
|
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(prog, ShouldHaveSameTypeAs, &runtime.Program{})
|
||||||
|
|
||||||
|
out, err := prog.Run(context.Background())
|
||||||
|
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(string(out), ShouldEqual, `[{"maxAge":69,"minAge":25}]`)
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("Should aggregate values without grouping with multiple arguments", t, func() {
|
||||||
|
c := compiler.New()
|
||||||
|
|
||||||
|
prog, err := c.Compile(`
|
||||||
|
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 ages = UNION(u.age, u.age)
|
||||||
|
RETURN ages
|
||||||
|
`)
|
||||||
|
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(prog, ShouldHaveSameTypeAs, &runtime.Program{})
|
||||||
|
|
||||||
|
out, err := prog.Run(context.Background())
|
||||||
|
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(string(out), ShouldEqual, `[[31,25,36,69,45,31,25,36,69,45]]`)
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("Should aggregate values with grouping", t, func() {
|
||||||
|
c := compiler.New()
|
||||||
|
|
||||||
|
prog, err := c.Compile(`
|
||||||
|
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 ageGroup = FLOOR(u.age / 5) * 5
|
||||||
|
AGGREGATE minAge = MIN(u.age), maxAge = MAX(u.age)
|
||||||
|
RETURN {
|
||||||
|
ageGroup,
|
||||||
|
minAge,
|
||||||
|
maxAge
|
||||||
|
}
|
||||||
|
`)
|
||||||
|
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(prog, ShouldHaveSameTypeAs, &runtime.Program{})
|
||||||
|
|
||||||
|
out, err := prog.Run(context.Background())
|
||||||
|
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(string(out), ShouldEqual, `[{"ageGroup":25,"maxAge":25,"minAge":25},{"ageGroup":30,"maxAge":31,"minAge":31},{"ageGroup":35,"maxAge":36,"minAge":36},{"ageGroup":45,"maxAge":45,"minAge":45},{"ageGroup":65,"maxAge":69,"minAge":69}]`)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkAggregate(b *testing.B) {
|
||||||
|
p := compiler.New().MustCompile(`
|
||||||
|
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)
|
||||||
|
RETURN {
|
||||||
|
minAge,
|
||||||
|
maxAge
|
||||||
|
}
|
||||||
|
`)
|
||||||
|
|
||||||
|
for n := 0; n < b.N; n++ {
|
||||||
|
p.Run(context.Background())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkAggregate2(b *testing.B) {
|
||||||
|
p := compiler.New().MustCompile(`
|
||||||
|
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 ages = UNION(u.age, u.age)
|
||||||
|
RETURN ages
|
||||||
|
`)
|
||||||
|
|
||||||
|
for n := 0; n < b.N; n++ {
|
||||||
|
p.Run(context.Background())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkAggregate3(b *testing.B) {
|
||||||
|
p := compiler.New().MustCompile(`
|
||||||
|
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 ageGroup = FLOOR(u.age / 5) * 5
|
||||||
|
AGGREGATE minAge = MIN(u.age), maxAge = MAX(u.age)
|
||||||
|
RETURN {
|
||||||
|
ageGroup,
|
||||||
|
minAge,
|
||||||
|
maxAge
|
||||||
|
}
|
||||||
|
`)
|
||||||
|
|
||||||
|
for n := 0; n < b.N; n++ {
|
||||||
|
p.Run(context.Background())
|
||||||
|
}
|
||||||
|
}
|
105
pkg/compiler/compiler_collect_count_test.go
Normal file
105
pkg/compiler/compiler_collect_count_test.go
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
package compiler_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"github.com/MontFerret/ferret/pkg/compiler"
|
||||||
|
"github.com/MontFerret/ferret/pkg/runtime"
|
||||||
|
. "github.com/smartystreets/goconvey/convey"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestCollectCount(t *testing.T) {
|
||||||
|
Convey("Should count grouped values", t, func() {
|
||||||
|
c := compiler.New()
|
||||||
|
|
||||||
|
prog, err := c.Compile(`
|
||||||
|
LET users = [
|
||||||
|
{
|
||||||
|
active: true,
|
||||||
|
age: 31,
|
||||||
|
gender: "m",
|
||||||
|
married: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
active: true,
|
||||||
|
age: 25,
|
||||||
|
gender: "f",
|
||||||
|
married: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
active: true,
|
||||||
|
age: 36,
|
||||||
|
gender: "m",
|
||||||
|
married: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
active: false,
|
||||||
|
age: 69,
|
||||||
|
gender: "m",
|
||||||
|
married: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
active: true,
|
||||||
|
age: 45,
|
||||||
|
gender: "f",
|
||||||
|
married: true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
FOR i IN users
|
||||||
|
COLLECT WITH COUNT INTO c
|
||||||
|
RETURN c
|
||||||
|
`)
|
||||||
|
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(prog, ShouldHaveSameTypeAs, &runtime.Program{})
|
||||||
|
|
||||||
|
out, err := prog.Run(context.Background())
|
||||||
|
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(string(out), ShouldEqual, `[5]`)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkCollectCount(b *testing.B) {
|
||||||
|
p := compiler.New().MustCompile(`
|
||||||
|
LET users = [
|
||||||
|
{
|
||||||
|
active: true,
|
||||||
|
age: 31,
|
||||||
|
gender: "m",
|
||||||
|
married: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
active: true,
|
||||||
|
age: 25,
|
||||||
|
gender: "f",
|
||||||
|
married: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
active: true,
|
||||||
|
age: 36,
|
||||||
|
gender: "m",
|
||||||
|
married: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
active: false,
|
||||||
|
age: 69,
|
||||||
|
gender: "m",
|
||||||
|
married: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
active: true,
|
||||||
|
age: 45,
|
||||||
|
gender: "f",
|
||||||
|
married: true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
FOR i IN users
|
||||||
|
COLLECT WITH COUNT INTO c
|
||||||
|
RETURN c
|
||||||
|
`)
|
||||||
|
|
||||||
|
for n := 0; n < b.N; n++ {
|
||||||
|
p.Run(context.Background())
|
||||||
|
}
|
||||||
|
}
|
265
pkg/compiler/compiler_collect_into_test.go
Normal file
265
pkg/compiler/compiler_collect_into_test.go
Normal file
@ -0,0 +1,265 @@
|
|||||||
|
package compiler_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"github.com/MontFerret/ferret/pkg/compiler"
|
||||||
|
"github.com/MontFerret/ferret/pkg/runtime"
|
||||||
|
. "github.com/smartystreets/goconvey/convey"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestCollectInto(t *testing.T) {
|
||||||
|
Convey("Should create default projection", t, func() {
|
||||||
|
c := compiler.New()
|
||||||
|
|
||||||
|
prog, err := c.Compile(`
|
||||||
|
LET users = [
|
||||||
|
{
|
||||||
|
active: true,
|
||||||
|
age: 31,
|
||||||
|
gender: "m",
|
||||||
|
married: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
active: true,
|
||||||
|
age: 25,
|
||||||
|
gender: "f",
|
||||||
|
married: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
active: true,
|
||||||
|
age: 36,
|
||||||
|
gender: "m",
|
||||||
|
married: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
active: false,
|
||||||
|
age: 69,
|
||||||
|
gender: "m",
|
||||||
|
married: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
active: true,
|
||||||
|
age: 45,
|
||||||
|
gender: "f",
|
||||||
|
married: true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
FOR i IN users
|
||||||
|
COLLECT gender = i.gender INTO genders
|
||||||
|
RETURN {
|
||||||
|
gender,
|
||||||
|
values: genders
|
||||||
|
}
|
||||||
|
`)
|
||||||
|
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(prog, ShouldHaveSameTypeAs, &runtime.Program{})
|
||||||
|
|
||||||
|
out, err := prog.Run(context.Background())
|
||||||
|
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(string(out), ShouldEqual, `[{"gender":"f","values":[{"i":{"active":true,"age":25,"gender":"f","married":false}},{"i":{"active":true,"age":45,"gender":"f","married":true}}]},{"gender":"m","values":[{"i":{"active":true,"age":31,"gender":"m","married":true}},{"i":{"active":true,"age":36,"gender":"m","married":false}},{"i":{"active":false,"age":69,"gender":"m","married":true}}]}]`)
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("Should create custom projection", t, func() {
|
||||||
|
c := compiler.New()
|
||||||
|
|
||||||
|
prog, err := c.Compile(`
|
||||||
|
LET users = [
|
||||||
|
{
|
||||||
|
active: true,
|
||||||
|
age: 31,
|
||||||
|
gender: "m",
|
||||||
|
married: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
active: true,
|
||||||
|
age: 25,
|
||||||
|
gender: "f",
|
||||||
|
married: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
active: true,
|
||||||
|
age: 36,
|
||||||
|
gender: "m",
|
||||||
|
married: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
active: false,
|
||||||
|
age: 69,
|
||||||
|
gender: "m",
|
||||||
|
married: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
active: true,
|
||||||
|
age: 45,
|
||||||
|
gender: "f",
|
||||||
|
married: true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
FOR i IN users
|
||||||
|
COLLECT gender = i.gender INTO genders = { active: i.active }
|
||||||
|
RETURN {
|
||||||
|
gender,
|
||||||
|
values: genders
|
||||||
|
}
|
||||||
|
`)
|
||||||
|
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(prog, ShouldHaveSameTypeAs, &runtime.Program{})
|
||||||
|
|
||||||
|
out, err := prog.Run(context.Background())
|
||||||
|
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(string(out), ShouldEqual, `[{"gender":"f","values":[{"active":true},{"active":true}]},{"gender":"m","values":[{"active":true},{"active":true},{"active":false}]}]`)
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("Should create custom projection grouped by miltiple keys", t, func() {
|
||||||
|
c := compiler.New()
|
||||||
|
|
||||||
|
prog, err := c.Compile(`
|
||||||
|
LET users = [
|
||||||
|
{
|
||||||
|
active: true,
|
||||||
|
age: 31,
|
||||||
|
gender: "m",
|
||||||
|
married: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
active: true,
|
||||||
|
age: 25,
|
||||||
|
gender: "f",
|
||||||
|
married: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
active: true,
|
||||||
|
age: 36,
|
||||||
|
gender: "m",
|
||||||
|
married: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
active: false,
|
||||||
|
age: 69,
|
||||||
|
gender: "m",
|
||||||
|
married: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
active: true,
|
||||||
|
age: 45,
|
||||||
|
gender: "f",
|
||||||
|
married: true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
FOR i IN users
|
||||||
|
COLLECT gender = i.gender, age = i.age INTO genders = { active: i.active }
|
||||||
|
RETURN {
|
||||||
|
age,
|
||||||
|
gender,
|
||||||
|
values: genders
|
||||||
|
}
|
||||||
|
`)
|
||||||
|
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(prog, ShouldHaveSameTypeAs, &runtime.Program{})
|
||||||
|
|
||||||
|
out, err := prog.Run(context.Background())
|
||||||
|
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(string(out), ShouldEqual, `[{"age":25,"gender":"f","values":[{"active":true}]},{"age":45,"gender":"f","values":[{"active":true}]},{"age":31,"gender":"m","values":[{"active":true}]},{"age":36,"gender":"m","values":[{"active":true}]},{"age":69,"gender":"m","values":[{"active":false}]}]`)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkCollectInto(b *testing.B) {
|
||||||
|
p := compiler.New().MustCompile(`
|
||||||
|
LET users = [
|
||||||
|
{
|
||||||
|
active: true,
|
||||||
|
age: 31,
|
||||||
|
gender: "m",
|
||||||
|
married: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
active: true,
|
||||||
|
age: 25,
|
||||||
|
gender: "f",
|
||||||
|
married: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
active: true,
|
||||||
|
age: 36,
|
||||||
|
gender: "m",
|
||||||
|
married: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
active: false,
|
||||||
|
age: 69,
|
||||||
|
gender: "m",
|
||||||
|
married: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
active: true,
|
||||||
|
age: 45,
|
||||||
|
gender: "f",
|
||||||
|
married: true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
FOR i IN users
|
||||||
|
COLLECT gender = i.gender INTO genders
|
||||||
|
RETURN {
|
||||||
|
gender,
|
||||||
|
values: genders
|
||||||
|
}
|
||||||
|
`)
|
||||||
|
|
||||||
|
for n := 0; n < b.N; n++ {
|
||||||
|
p.Run(context.Background())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkCollectInto2(b *testing.B) {
|
||||||
|
p := compiler.New().MustCompile(`
|
||||||
|
LET users = [
|
||||||
|
{
|
||||||
|
active: true,
|
||||||
|
age: 31,
|
||||||
|
gender: "m",
|
||||||
|
married: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
active: true,
|
||||||
|
age: 25,
|
||||||
|
gender: "f",
|
||||||
|
married: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
active: true,
|
||||||
|
age: 36,
|
||||||
|
gender: "m",
|
||||||
|
married: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
active: false,
|
||||||
|
age: 69,
|
||||||
|
gender: "m",
|
||||||
|
married: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
active: true,
|
||||||
|
age: 45,
|
||||||
|
gender: "f",
|
||||||
|
married: true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
FOR i IN users
|
||||||
|
COLLECT gender = i.gender INTO genders = { active: i.active }
|
||||||
|
RETURN {
|
||||||
|
gender,
|
||||||
|
values: genders
|
||||||
|
}
|
||||||
|
`)
|
||||||
|
|
||||||
|
for n := 0; n < b.N; n++ {
|
||||||
|
p.Run(context.Background())
|
||||||
|
}
|
||||||
|
}
|
246
pkg/compiler/compiler_collect_test.go
Normal file
246
pkg/compiler/compiler_collect_test.go
Normal file
@ -0,0 +1,246 @@
|
|||||||
|
package compiler_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"github.com/MontFerret/ferret/pkg/compiler"
|
||||||
|
"github.com/MontFerret/ferret/pkg/runtime"
|
||||||
|
. "github.com/smartystreets/goconvey/convey"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestCollect(t *testing.T) {
|
||||||
|
Convey("Should not have access to initial variables", t, func() {
|
||||||
|
c := compiler.New()
|
||||||
|
|
||||||
|
_, err := c.Compile(`
|
||||||
|
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 users
|
||||||
|
COLLECT gender = i.gender
|
||||||
|
RETURN {
|
||||||
|
user: i,
|
||||||
|
gender: gender
|
||||||
|
}
|
||||||
|
`)
|
||||||
|
|
||||||
|
So(err, ShouldNotBeNil)
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("Should group result by a single key", t, func() {
|
||||||
|
c := compiler.New()
|
||||||
|
|
||||||
|
prog, err := c.Compile(`
|
||||||
|
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 users
|
||||||
|
COLLECT gender = i.gender
|
||||||
|
RETURN gender
|
||||||
|
`)
|
||||||
|
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(prog, ShouldHaveSameTypeAs, &runtime.Program{})
|
||||||
|
|
||||||
|
out, err := prog.Run(context.Background())
|
||||||
|
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(string(out), ShouldEqual, `["f","m"]`)
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("Should group result by multiple keys", t, func() {
|
||||||
|
c := compiler.New()
|
||||||
|
|
||||||
|
prog, err := c.Compile(`
|
||||||
|
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 users
|
||||||
|
COLLECT gender = i.gender, age = i.age
|
||||||
|
RETURN {age, gender}
|
||||||
|
`)
|
||||||
|
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(prog, ShouldHaveSameTypeAs, &runtime.Program{})
|
||||||
|
|
||||||
|
out, err := prog.Run(context.Background())
|
||||||
|
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(string(out), ShouldEqual, `[{"age":25,"gender":"f"},{"age":45,"gender":"f"},{"age":31,"gender":"m"},{"age":36,"gender":"m"},{"age":69,"gender":"m"}]`)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkCollect(b *testing.B) {
|
||||||
|
p := compiler.New().MustCompile(`
|
||||||
|
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 users
|
||||||
|
COLLECT gender = i.gender
|
||||||
|
RETURN gender
|
||||||
|
`)
|
||||||
|
|
||||||
|
for n := 0; n < b.N; n++ {
|
||||||
|
p.Run(context.Background())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkCollect2(b *testing.B) {
|
||||||
|
p := compiler.New().MustCompile(`
|
||||||
|
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 users
|
||||||
|
COLLECT gender = i.gender, age = i.age
|
||||||
|
RETURN {age, gender}
|
||||||
|
`)
|
||||||
|
|
||||||
|
for n := 0; n < b.N; n++ {
|
||||||
|
p.Run(context.Background())
|
||||||
|
}
|
||||||
|
}
|
199
pkg/compiler/compiler_collect_with_count_test.go
Normal file
199
pkg/compiler/compiler_collect_with_count_test.go
Normal file
@ -0,0 +1,199 @@
|
|||||||
|
package compiler_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"github.com/MontFerret/ferret/pkg/compiler"
|
||||||
|
"github.com/MontFerret/ferret/pkg/runtime"
|
||||||
|
. "github.com/smartystreets/goconvey/convey"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestCollectWithCount(t *testing.T) {
|
||||||
|
Convey("Should count grouped values", t, func() {
|
||||||
|
c := compiler.New()
|
||||||
|
|
||||||
|
prog, err := c.Compile(`
|
||||||
|
LET users = [
|
||||||
|
{
|
||||||
|
active: true,
|
||||||
|
age: 31,
|
||||||
|
gender: "m",
|
||||||
|
married: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
active: true,
|
||||||
|
age: 25,
|
||||||
|
gender: "f",
|
||||||
|
married: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
active: true,
|
||||||
|
age: 36,
|
||||||
|
gender: "m",
|
||||||
|
married: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
active: false,
|
||||||
|
age: 69,
|
||||||
|
gender: "m",
|
||||||
|
married: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
active: true,
|
||||||
|
age: 45,
|
||||||
|
gender: "f",
|
||||||
|
married: true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
FOR i IN users
|
||||||
|
COLLECT gender = i.gender WITH COUNT INTO genders
|
||||||
|
RETURN {gender, genders}
|
||||||
|
`)
|
||||||
|
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(prog, ShouldHaveSameTypeAs, &runtime.Program{})
|
||||||
|
|
||||||
|
out, err := prog.Run(context.Background())
|
||||||
|
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(string(out), ShouldEqual, `[{"gender":"f","genders":2},{"gender":"m","genders":3}]`)
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("Should count grouped values with multiple keys", t, func() {
|
||||||
|
c := compiler.New()
|
||||||
|
|
||||||
|
prog, err := c.Compile(`
|
||||||
|
LET users = [
|
||||||
|
{
|
||||||
|
active: true,
|
||||||
|
age: 31,
|
||||||
|
gender: "m",
|
||||||
|
married: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
active: true,
|
||||||
|
age: 25,
|
||||||
|
gender: "f",
|
||||||
|
married: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
active: true,
|
||||||
|
age: 36,
|
||||||
|
gender: "m",
|
||||||
|
married: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
active: false,
|
||||||
|
age: 69,
|
||||||
|
gender: "m",
|
||||||
|
married: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
active: true,
|
||||||
|
age: 45,
|
||||||
|
gender: "f",
|
||||||
|
married: true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
FOR i IN users
|
||||||
|
COLLECT gender = i.gender, married = i.married WITH COUNT INTO genders
|
||||||
|
RETURN {gender, married, genders}
|
||||||
|
`)
|
||||||
|
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(prog, ShouldHaveSameTypeAs, &runtime.Program{})
|
||||||
|
|
||||||
|
out, err := prog.Run(context.Background())
|
||||||
|
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(string(out), ShouldEqual, `[{"gender":"f","genders":1,"married":false},{"gender":"f","genders":1,"married":true},{"gender":"m","genders":1,"married":false},{"gender":"m","genders":2,"married":true}]`)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkWithCount(b *testing.B) {
|
||||||
|
p := compiler.New().MustCompile(`
|
||||||
|
LET users = [
|
||||||
|
{
|
||||||
|
active: true,
|
||||||
|
age: 31,
|
||||||
|
gender: "m",
|
||||||
|
married: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
active: true,
|
||||||
|
age: 25,
|
||||||
|
gender: "f",
|
||||||
|
married: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
active: true,
|
||||||
|
age: 36,
|
||||||
|
gender: "m",
|
||||||
|
married: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
active: false,
|
||||||
|
age: 69,
|
||||||
|
gender: "m",
|
||||||
|
married: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
active: true,
|
||||||
|
age: 45,
|
||||||
|
gender: "f",
|
||||||
|
married: true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
FOR i IN users
|
||||||
|
COLLECT gender = i.gender WITH COUNT INTO genders
|
||||||
|
RETURN {gender, genders}
|
||||||
|
`)
|
||||||
|
|
||||||
|
for n := 0; n < b.N; n++ {
|
||||||
|
p.Run(context.Background())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkWithCount2(b *testing.B) {
|
||||||
|
p := compiler.New().MustCompile(`
|
||||||
|
LET users = [
|
||||||
|
{
|
||||||
|
active: true,
|
||||||
|
age: 31,
|
||||||
|
gender: "m",
|
||||||
|
married: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
active: true,
|
||||||
|
age: 25,
|
||||||
|
gender: "f",
|
||||||
|
married: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
active: true,
|
||||||
|
age: 36,
|
||||||
|
gender: "m",
|
||||||
|
married: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
active: false,
|
||||||
|
age: 69,
|
||||||
|
gender: "m",
|
||||||
|
married: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
active: true,
|
||||||
|
age: 45,
|
||||||
|
gender: "f",
|
||||||
|
married: true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
FOR i IN users
|
||||||
|
COLLECT gender = i.gender, married = i.married WITH COUNT INTO genders
|
||||||
|
RETURN {gender, married, genders}
|
||||||
|
`)
|
||||||
|
|
||||||
|
for n := 0; n < b.N; n++ {
|
||||||
|
p.Run(context.Background())
|
||||||
|
}
|
||||||
|
}
|
@ -47,13 +47,14 @@ func TestParam(t *testing.T) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
Convey("Should be possible to use in range", t, func() {
|
Convey("Should be possible to use in range", t, func() {
|
||||||
out := compiler.New().
|
prog := compiler.New().
|
||||||
MustCompile(`
|
MustCompile(`
|
||||||
FOR i IN @start..@end
|
FOR i IN @start..@end
|
||||||
SORT i
|
SORT i
|
||||||
RETURN i
|
RETURN i
|
||||||
`).
|
`)
|
||||||
MustRun(
|
|
||||||
|
out := prog.MustRun(
|
||||||
context.Background(),
|
context.Background(),
|
||||||
runtime.WithParam("start", 1),
|
runtime.WithParam("start", 1),
|
||||||
runtime.WithParam("end", 4),
|
runtime.WithParam("end", 4),
|
||||||
|
@ -58,6 +58,18 @@ func (s *scope) SetVariable(name string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *scope) RemoveVariable(name string) error {
|
||||||
|
_, exists := s.vars[name]
|
||||||
|
|
||||||
|
if !exists {
|
||||||
|
return errors.Wrap(ErrVariableNotFound, name)
|
||||||
|
}
|
||||||
|
|
||||||
|
delete(s.vars, name)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (s *scope) Fork() *scope {
|
func (s *scope) Fork() *scope {
|
||||||
return newScope(s)
|
return newScope(s)
|
||||||
}
|
}
|
||||||
|
@ -16,11 +16,15 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
type visitor struct {
|
type (
|
||||||
|
forOption func(f *expressions.ForExpression) error
|
||||||
|
|
||||||
|
visitor struct {
|
||||||
*fql.BaseFqlParserVisitor
|
*fql.BaseFqlParserVisitor
|
||||||
src string
|
src string
|
||||||
funcs map[string]core.Function
|
funcs map[string]core.Function
|
||||||
}
|
}
|
||||||
|
)
|
||||||
|
|
||||||
func newVisitor(src string, funcs map[string]core.Function) *visitor {
|
func newVisitor(src string, funcs map[string]core.Function) *visitor {
|
||||||
return &visitor{
|
return &visitor{
|
||||||
@ -155,6 +159,7 @@ func (v *visitor) doVisitForExpression(ctx *fql.ForExpressionContext, scope *sco
|
|||||||
var valVarName string
|
var valVarName string
|
||||||
var keyVarName string
|
var keyVarName string
|
||||||
|
|
||||||
|
parsedClauses := make([]forOption, 0, 10)
|
||||||
valVar := ctx.ForExpressionValueVariable()
|
valVar := ctx.ForExpressionValueVariable()
|
||||||
valVarName = valVar.GetText()
|
valVarName = valVar.GetText()
|
||||||
forInScope := scope.Fork()
|
forInScope := scope.Fork()
|
||||||
@ -167,12 +172,95 @@ func (v *visitor) doVisitForExpression(ctx *fql.ForExpressionContext, scope *sco
|
|||||||
forInScope.SetVariable(keyVarName)
|
forInScope.SetVariable(keyVarName)
|
||||||
}
|
}
|
||||||
|
|
||||||
src, err := v.doVisitForExpressionSource(ctx.ForExpressionSource().(*fql.ForExpressionSourceContext), forInScope)
|
srcCtx := ctx.ForExpressionSource().(*fql.ForExpressionSourceContext)
|
||||||
|
srcExp, err := v.doVisitForExpressionSource(srcCtx, forInScope)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
src, err := expressions.NewDataSource(
|
||||||
|
v.getSourceMap(srcCtx),
|
||||||
|
valVarName,
|
||||||
|
keyVarName,
|
||||||
|
srcExp,
|
||||||
|
)
|
||||||
|
|
||||||
|
// Clauses.
|
||||||
|
// We put clauses parsing before parsing the query body because COLLECT clause overrides scope variables
|
||||||
|
for _, clause := range ctx.AllForExpressionClause() {
|
||||||
|
clause := clause.(*fql.ForExpressionClauseContext)
|
||||||
|
|
||||||
|
limitCtx := clause.LimitClause()
|
||||||
|
|
||||||
|
if limitCtx != nil {
|
||||||
|
limit, offset, err := v.createLimit(limitCtx.(*fql.LimitClauseContext))
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
parsedClauses = append(parsedClauses, func(f *expressions.ForExpression) error {
|
||||||
|
return f.AddLimit(v.getSourceMap(limitCtx), limit, offset)
|
||||||
|
})
|
||||||
|
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
filterCtx := clause.FilterClause()
|
||||||
|
|
||||||
|
if filterCtx != nil {
|
||||||
|
filterExp, err := v.createFilter(filterCtx.(*fql.FilterClauseContext), forInScope)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
parsedClauses = append(parsedClauses, func(f *expressions.ForExpression) error {
|
||||||
|
return f.AddFilter(v.getSourceMap(filterCtx), filterExp)
|
||||||
|
})
|
||||||
|
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
sortCtx := clause.SortClause()
|
||||||
|
|
||||||
|
if sortCtx != nil {
|
||||||
|
sortCtx := sortCtx.(*fql.SortClauseContext)
|
||||||
|
sortExps, err := v.createSort(sortCtx, forInScope)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
parsedClauses = append(parsedClauses, func(f *expressions.ForExpression) error {
|
||||||
|
return f.AddSort(v.getSourceMap(sortCtx), sortExps...)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
collectCtx := clause.CollectClause()
|
||||||
|
|
||||||
|
if collectCtx != nil {
|
||||||
|
collectCtx := collectCtx.(*fql.CollectClauseContext)
|
||||||
|
|
||||||
|
params, err := v.createCollect(collectCtx, forInScope, valVarName)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
forInScope.RemoveVariable(valVarName)
|
||||||
|
|
||||||
|
if keyVarName != "" {
|
||||||
|
forInScope.RemoveVariable(keyVarName)
|
||||||
|
}
|
||||||
|
|
||||||
|
parsedClauses = append(parsedClauses, func(f *expressions.ForExpression) error {
|
||||||
|
return f.AddCollect(v.getSourceMap(collectCtx), params)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
body := ctx.AllForExpressionBody()
|
body := ctx.AllForExpressionBody()
|
||||||
predicate := expressions.NewBlockExpression(len(body) + 1)
|
predicate := expressions.NewBlockExpression(len(body) + 1)
|
||||||
|
|
||||||
@ -192,7 +280,6 @@ func (v *visitor) doVisitForExpression(ctx *fql.ForExpressionContext, scope *sco
|
|||||||
|
|
||||||
var spread bool
|
var spread bool
|
||||||
var distinct bool
|
var distinct bool
|
||||||
var distinctSrc core.SourceMap
|
|
||||||
forRetCtx := ctx.ForExpressionReturn().(*fql.ForExpressionReturnContext)
|
forRetCtx := ctx.ForExpressionReturn().(*fql.ForExpressionReturnContext)
|
||||||
returnCtx := forRetCtx.ReturnExpression()
|
returnCtx := forRetCtx.ReturnExpression()
|
||||||
|
|
||||||
@ -208,8 +295,6 @@ func (v *visitor) doVisitForExpression(ctx *fql.ForExpressionContext, scope *sco
|
|||||||
|
|
||||||
if distinctCtx != nil {
|
if distinctCtx != nil {
|
||||||
distinct = true
|
distinct = true
|
||||||
token := distinctCtx.GetSymbol()
|
|
||||||
distinctSrc = core.NewSourceMap(token.GetText(), token.GetLine(), token.GetColumn())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
predicate.Add(returnExp)
|
predicate.Add(returnExp)
|
||||||
@ -228,10 +313,9 @@ func (v *visitor) doVisitForExpression(ctx *fql.ForExpressionContext, scope *sco
|
|||||||
|
|
||||||
forExp, err := expressions.NewForExpression(
|
forExp, err := expressions.NewForExpression(
|
||||||
v.getSourceMap(ctx),
|
v.getSourceMap(ctx),
|
||||||
valVarName,
|
|
||||||
keyVarName,
|
|
||||||
src,
|
src,
|
||||||
predicate,
|
predicate,
|
||||||
|
distinct,
|
||||||
spread,
|
spread,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -239,53 +323,11 @@ func (v *visitor) doVisitForExpression(ctx *fql.ForExpressionContext, scope *sco
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if distinct {
|
// add all available clauses
|
||||||
forExp.AddDistinct(distinctSrc)
|
for _, clause := range parsedClauses {
|
||||||
}
|
if err := clause(forExp); err != nil {
|
||||||
|
|
||||||
for _, clause := range ctx.AllForExpressionClause() {
|
|
||||||
clause := clause.(*fql.ForExpressionClauseContext)
|
|
||||||
|
|
||||||
limitCtx := clause.LimitClause()
|
|
||||||
|
|
||||||
if limitCtx != nil {
|
|
||||||
limit, offset, err := v.createLimit(limitCtx.(*fql.LimitClauseContext))
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
forExp.AddLimit(v.getSourceMap(limitCtx), limit, offset)
|
|
||||||
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
filterCtx := clause.FilterClause()
|
|
||||||
|
|
||||||
if filterCtx != nil {
|
|
||||||
filterExp, err := v.createFilter(filterCtx.(*fql.FilterClauseContext), forInScope)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
forExp.AddFilter(v.getSourceMap(filterCtx), filterExp)
|
|
||||||
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
sortCtx := clause.SortClause()
|
|
||||||
|
|
||||||
if sortCtx != nil {
|
|
||||||
sortCtx := sortCtx.(*fql.SortClauseContext)
|
|
||||||
sortExps, err := v.createSort(sortCtx, forInScope)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
forExp.AddSort(v.getSourceMap(sortCtx), sortExps...)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return forExp, nil
|
return forExp, nil
|
||||||
@ -392,7 +434,190 @@ func (v *visitor) createSort(ctx *fql.SortClauseContext, scope *scope) ([]*claus
|
|||||||
return res, nil
|
return res, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *visitor) doVisitForExpressionSource(ctx *fql.ForExpressionSourceContext, scope *scope) (collections.IterableExpression, error) {
|
func (v *visitor) createCollect(ctx *fql.CollectClauseContext, scope *scope, valVarName string) (*clauses.Collect, error) {
|
||||||
|
var err error
|
||||||
|
var selectors []*clauses.CollectSelector
|
||||||
|
var projection *clauses.CollectProjection
|
||||||
|
var count *clauses.CollectCount
|
||||||
|
var aggregate *clauses.CollectAggregate
|
||||||
|
|
||||||
|
groupingCtx := ctx.CollectGrouping()
|
||||||
|
|
||||||
|
if groupingCtx != nil {
|
||||||
|
groupingCtx := groupingCtx.(*fql.CollectGroupingContext)
|
||||||
|
collectSelectors := groupingCtx.AllCollectSelector()
|
||||||
|
|
||||||
|
// group selectors
|
||||||
|
if collectSelectors != nil && len(collectSelectors) > 0 {
|
||||||
|
selectors = make([]*clauses.CollectSelector, 0, len(collectSelectors))
|
||||||
|
|
||||||
|
for _, cs := range collectSelectors {
|
||||||
|
selector, err := v.createCollectSelector(cs.(*fql.CollectSelectorContext), scope)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
selectors = append(selectors, selector)
|
||||||
|
|
||||||
|
if err := scope.SetVariable(selector.Variable()); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
projectionCtx := ctx.CollectGroupVariable()
|
||||||
|
|
||||||
|
if projectionCtx != nil {
|
||||||
|
projectionCtx := projectionCtx.(*fql.CollectGroupVariableContext)
|
||||||
|
projectionSelectorCtx := projectionCtx.CollectSelector()
|
||||||
|
var projectionSelector *clauses.CollectSelector
|
||||||
|
|
||||||
|
// if projection expression is defined like WITH group = { foo: i.bar }
|
||||||
|
if projectionSelectorCtx != nil {
|
||||||
|
selector, err := v.createCollectSelector(projectionSelectorCtx.(*fql.CollectSelectorContext), scope)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
projectionSelector = selector
|
||||||
|
} else {
|
||||||
|
// otherwise, use default expression WITH group = { i }
|
||||||
|
projectionIdentifier := projectionCtx.Identifier(0)
|
||||||
|
|
||||||
|
if projectionIdentifier != nil {
|
||||||
|
varExp, err := expressions.NewVariableExpression(v.getSourceMap(projectionCtx), valVarName)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
strLitExp := literals.NewStringLiteral(valVarName)
|
||||||
|
|
||||||
|
propExp, err := literals.NewObjectPropertyAssignment(
|
||||||
|
strLitExp,
|
||||||
|
varExp,
|
||||||
|
)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
projectionSelectorExp := literals.NewObjectLiteralWith(propExp)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
selector, err := clauses.NewCollectSelector(projectionIdentifier.GetText(), projectionSelectorExp)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
projectionSelector = selector
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if projectionSelector != nil {
|
||||||
|
if err := scope.SetVariable(projectionSelector.Variable()); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
projection, err = clauses.NewCollectProjection(projectionSelector)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
countCtx := ctx.CollectCounter()
|
||||||
|
|
||||||
|
if countCtx != nil {
|
||||||
|
countCtx := countCtx.(*fql.CollectCounterContext)
|
||||||
|
variable := countCtx.Identifier().GetText()
|
||||||
|
|
||||||
|
if err := scope.SetVariable(variable); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
count, err = clauses.NewCollectCount(variable)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
aggrCtx := ctx.CollectAggregator()
|
||||||
|
|
||||||
|
if aggrCtx != nil {
|
||||||
|
aggrCtx := aggrCtx.(*fql.CollectAggregatorContext)
|
||||||
|
|
||||||
|
selectorCtxs := aggrCtx.AllCollectAggregateSelector()
|
||||||
|
selectors := make([]*clauses.CollectAggregateSelector, 0, len(selectorCtxs))
|
||||||
|
|
||||||
|
for _, sc := range selectorCtxs {
|
||||||
|
selector, err := v.createCollectAggregateSelector(sc.(*fql.CollectAggregateSelectorContext), scope)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
selectors = append(selectors, selector)
|
||||||
|
}
|
||||||
|
|
||||||
|
aggregate, err = clauses.NewCollectAggregate(selectors)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return clauses.NewCollect(selectors, projection, count, aggregate)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *visitor) createCollectSelector(ctx *fql.CollectSelectorContext, scope *scope) (*clauses.CollectSelector, error) {
|
||||||
|
variable := ctx.Identifier().GetText()
|
||||||
|
exp, err := v.doVisitExpression(ctx.Expression().(*fql.ExpressionContext), scope)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return clauses.NewCollectSelector(variable, exp)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *visitor) createCollectAggregateSelector(ctx *fql.CollectAggregateSelectorContext, scope *scope) (*clauses.CollectAggregateSelector, error) {
|
||||||
|
variable := ctx.Identifier().GetText()
|
||||||
|
fnCtx := ctx.FunctionCallExpression()
|
||||||
|
|
||||||
|
if fnCtx != nil {
|
||||||
|
exp, err := v.doVisitFunctionCallExpression(fnCtx.(*fql.FunctionCallExpressionContext), scope)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
fnExp, ok := exp.(*expressions.FunctionCallExpression)
|
||||||
|
|
||||||
|
if !ok {
|
||||||
|
return nil, core.Error(core.ErrInvalidType, "expected function expression")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := scope.SetVariable(variable); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return clauses.NewCollectAggregateSelector(variable, fnExp.Arguments(), fnExp.Function())
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, core.Error(core.ErrNotFound, "function expression")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *visitor) doVisitForExpressionSource(ctx *fql.ForExpressionSourceContext, scope *scope) (core.Expression, error) {
|
||||||
arr := ctx.ArrayLiteral()
|
arr := ctx.ArrayLiteral()
|
||||||
|
|
||||||
if arr != nil {
|
if arr != nil {
|
||||||
@ -454,7 +679,7 @@ func (v *visitor) doVisitForExpressionBody(ctx *fql.ForExpressionBodyContext, sc
|
|||||||
return nil, v.unexpectedToken(ctx)
|
return nil, v.unexpectedToken(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *visitor) doVisitMemberExpression(ctx *fql.MemberExpressionContext, scope *scope) (collections.IterableExpression, error) {
|
func (v *visitor) doVisitMemberExpression(ctx *fql.MemberExpressionContext, scope *scope) (core.Expression, error) {
|
||||||
varName := ctx.Identifier().GetText()
|
varName := ctx.Identifier().GetText()
|
||||||
|
|
||||||
_, err := scope.GetVariable(varName)
|
_, err := scope.GetVariable(varName)
|
||||||
@ -516,7 +741,7 @@ func (v *visitor) doVisitMemberExpression(ctx *fql.MemberExpressionContext, scop
|
|||||||
return member, nil
|
return member, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *visitor) doVisitObjectLiteral(ctx *fql.ObjectLiteralContext, scope *scope) (collections.IterableExpression, error) {
|
func (v *visitor) doVisitObjectLiteral(ctx *fql.ObjectLiteralContext, scope *scope) (core.Expression, error) {
|
||||||
assignments := ctx.AllPropertyAssignment()
|
assignments := ctx.AllPropertyAssignment()
|
||||||
props := make([]*literals.ObjectPropertyAssignment, 0, len(assignments))
|
props := make([]*literals.ObjectPropertyAssignment, 0, len(assignments))
|
||||||
|
|
||||||
@ -553,7 +778,13 @@ func (v *visitor) doVisitObjectLiteral(ctx *fql.ObjectLiteralContext, scope *sco
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
props = append(props, literals.NewObjectPropertyAssignment(name, value))
|
pa, err := literals.NewObjectPropertyAssignment(name, value)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
props = append(props, pa)
|
||||||
}
|
}
|
||||||
|
|
||||||
return literals.NewObjectLiteralWith(props...), nil
|
return literals.NewObjectLiteralWith(props...), nil
|
||||||
@ -598,7 +829,7 @@ func (v *visitor) doVisitShorthandPropertyNameContext(ctx *fql.ShorthandProperty
|
|||||||
return literals.NewStringLiteral(ctx.Variable().GetText()), nil
|
return literals.NewStringLiteral(ctx.Variable().GetText()), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *visitor) doVisitArrayLiteral(ctx *fql.ArrayLiteralContext, scope *scope) (collections.IterableExpression, error) {
|
func (v *visitor) doVisitArrayLiteral(ctx *fql.ArrayLiteralContext, scope *scope) (core.Expression, error) {
|
||||||
listCtx := ctx.ArrayElementList()
|
listCtx := ctx.ArrayElementList()
|
||||||
|
|
||||||
if listCtx == nil {
|
if listCtx == nil {
|
||||||
@ -657,7 +888,7 @@ func (v *visitor) doVisitNoneLiteral(_ *fql.NoneLiteralContext) (core.Expression
|
|||||||
return literals.None, nil
|
return literals.None, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *visitor) doVisitVariable(ctx *fql.VariableContext, scope *scope) (collections.IterableExpression, error) {
|
func (v *visitor) doVisitVariable(ctx *fql.VariableContext, scope *scope) (core.Expression, error) {
|
||||||
name := ctx.Identifier().GetText()
|
name := ctx.Identifier().GetText()
|
||||||
|
|
||||||
// check whether the variable is defined
|
// check whether the variable is defined
|
||||||
@ -711,7 +942,7 @@ func (v *visitor) doVisitVariableDeclaration(ctx *fql.VariableDeclarationContext
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *visitor) doVisitRangeOperator(ctx *fql.RangeOperatorContext, scope *scope) (collections.IterableExpression, error) {
|
func (v *visitor) doVisitRangeOperator(ctx *fql.RangeOperatorContext, scope *scope) (core.Expression, error) {
|
||||||
exp, err := v.doVisitChildren(ctx, scope)
|
exp, err := v.doVisitChildren(ctx, scope)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -732,7 +963,7 @@ func (v *visitor) doVisitRangeOperator(ctx *fql.RangeOperatorContext, scope *sco
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *visitor) doVisitFunctionCallExpression(context *fql.FunctionCallExpressionContext, scope *scope) (collections.IterableExpression, error) {
|
func (v *visitor) doVisitFunctionCallExpression(context *fql.FunctionCallExpressionContext, scope *scope) (core.Expression, error) {
|
||||||
args := make([]core.Expression, 0, 5)
|
args := make([]core.Expression, 0, 5)
|
||||||
argsCtx := context.Arguments()
|
argsCtx := context.Arguments()
|
||||||
|
|
||||||
@ -765,7 +996,7 @@ func (v *visitor) doVisitFunctionCallExpression(context *fql.FunctionCallExpress
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *visitor) doVisitParamContext(context *fql.ParamContext, _ *scope) (collections.IterableExpression, error) {
|
func (v *visitor) doVisitParamContext(context *fql.ParamContext, _ *scope) (core.Expression, error) {
|
||||||
name := context.Identifier().GetText()
|
name := context.Identifier().GetText()
|
||||||
|
|
||||||
return expressions.NewParameterExpression(
|
return expressions.NewParameterExpression(
|
||||||
|
@ -75,41 +75,37 @@ sortClauseExpression
|
|||||||
;
|
;
|
||||||
|
|
||||||
collectClause
|
collectClause
|
||||||
: Collect collectVariable Assign expression
|
: Collect collectCounter
|
||||||
| Collect collectVariable Assign expression Into collectGroupVariable
|
| Collect collectAggregator
|
||||||
| Collect collectVariable Assign expression Into collectGroupVariable Keep collectKeepVariable
|
| Collect collectGrouping collectAggregator
|
||||||
| Collect collectVariable Assign expression With Count collectCountVariable
|
| Collect collectGrouping collectGroupVariable
|
||||||
| Collect collectVariable Assign expression Aggregate collectAggregateVariable Assign collectAggregateExpression
|
| Collect collectGrouping collectCounter
|
||||||
| Collect Aggregate collectAggregateVariable Assign collectAggregateExpression
|
| Collect collectGrouping
|
||||||
| Collect With Count Into collectCountVariable
|
|
||||||
;
|
;
|
||||||
|
|
||||||
collectVariable
|
collectSelector
|
||||||
: Identifier
|
: Identifier Assign expression
|
||||||
|
;
|
||||||
|
|
||||||
|
collectGrouping
|
||||||
|
: collectSelector (Comma collectSelector)*
|
||||||
|
;
|
||||||
|
|
||||||
|
collectAggregator
|
||||||
|
: Aggregate collectAggregateSelector (Comma collectAggregateSelector)*
|
||||||
|
;
|
||||||
|
|
||||||
|
collectAggregateSelector
|
||||||
|
: Identifier Assign functionCallExpression
|
||||||
;
|
;
|
||||||
|
|
||||||
collectGroupVariable
|
collectGroupVariable
|
||||||
: Identifier
|
: Into collectSelector
|
||||||
|
| Into Identifier (Keep Identifier)?
|
||||||
;
|
;
|
||||||
|
|
||||||
collectKeepVariable
|
collectCounter
|
||||||
: Identifier
|
: With Count Into Identifier
|
||||||
;
|
|
||||||
|
|
||||||
collectCountVariable
|
|
||||||
: Identifier
|
|
||||||
;
|
|
||||||
|
|
||||||
collectAggregateVariable
|
|
||||||
: Identifier
|
|
||||||
;
|
|
||||||
|
|
||||||
collectAggregateExpression
|
|
||||||
: expression
|
|
||||||
;
|
|
||||||
|
|
||||||
collectOption
|
|
||||||
:
|
|
||||||
;
|
;
|
||||||
|
|
||||||
forExpressionBody
|
forExpressionBody
|
||||||
|
File diff suppressed because one or more lines are too long
@ -276,7 +276,7 @@ var lexerSymbolicNames = []string{
|
|||||||
"And", "Or", "Range", "Assign", "QuestionMark", "RegexNotMatch", "RegexMatch",
|
"And", "Or", "Range", "Assign", "QuestionMark", "RegexNotMatch", "RegexMatch",
|
||||||
"For", "Return", "Distinct", "Filter", "Sort", "Limit", "Let", "Collect",
|
"For", "Return", "Distinct", "Filter", "Sort", "Limit", "Let", "Collect",
|
||||||
"SortDirection", "None", "Null", "BooleanLiteral", "Into", "Keep", "With",
|
"SortDirection", "None", "Null", "BooleanLiteral", "Into", "Keep", "With",
|
||||||
"Count", "All", "Any", "Aggregate", "Like", "Not", "In", "Param", "Identifier",
|
"count", "All", "Any", "aggregate", "Like", "Not", "In", "Param", "Identifier",
|
||||||
"StringLiteral", "TemplateStringLiteral", "IntegerLiteral", "FloatLiteral",
|
"StringLiteral", "TemplateStringLiteral", "IntegerLiteral", "FloatLiteral",
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -288,7 +288,7 @@ var lexerRuleNames = []string{
|
|||||||
"And", "Or", "Range", "Assign", "QuestionMark", "RegexNotMatch", "RegexMatch",
|
"And", "Or", "Range", "Assign", "QuestionMark", "RegexNotMatch", "RegexMatch",
|
||||||
"For", "Return", "Distinct", "Filter", "Sort", "Limit", "Let", "Collect",
|
"For", "Return", "Distinct", "Filter", "Sort", "Limit", "Let", "Collect",
|
||||||
"SortDirection", "None", "Null", "BooleanLiteral", "Into", "Keep", "With",
|
"SortDirection", "None", "Null", "BooleanLiteral", "Into", "Keep", "With",
|
||||||
"Count", "All", "Any", "Aggregate", "Like", "Not", "In", "Param", "Identifier",
|
"count", "All", "Any", "aggregate", "Like", "Not", "In", "Param", "Identifier",
|
||||||
"StringLiteral", "TemplateStringLiteral", "IntegerLiteral", "FloatLiteral",
|
"StringLiteral", "TemplateStringLiteral", "IntegerLiteral", "FloatLiteral",
|
||||||
"HexDigit", "DecimalIntegerLiteral", "ExponentPart", "Letter", "Symbols",
|
"HexDigit", "DecimalIntegerLiteral", "ExponentPart", "Letter", "Symbols",
|
||||||
"Digit", "DQSring", "SQString",
|
"Digit", "DQSring", "SQString",
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -112,11 +112,29 @@ func (s *BaseFqlParserListener) EnterCollectClause(ctx *CollectClauseContext) {}
|
|||||||
// ExitCollectClause is called when production collectClause is exited.
|
// ExitCollectClause is called when production collectClause is exited.
|
||||||
func (s *BaseFqlParserListener) ExitCollectClause(ctx *CollectClauseContext) {}
|
func (s *BaseFqlParserListener) ExitCollectClause(ctx *CollectClauseContext) {}
|
||||||
|
|
||||||
// EnterCollectVariable is called when production collectVariable is entered.
|
// EnterCollectSelector is called when production collectSelector is entered.
|
||||||
func (s *BaseFqlParserListener) EnterCollectVariable(ctx *CollectVariableContext) {}
|
func (s *BaseFqlParserListener) EnterCollectSelector(ctx *CollectSelectorContext) {}
|
||||||
|
|
||||||
// ExitCollectVariable is called when production collectVariable is exited.
|
// ExitCollectSelector is called when production collectSelector is exited.
|
||||||
func (s *BaseFqlParserListener) ExitCollectVariable(ctx *CollectVariableContext) {}
|
func (s *BaseFqlParserListener) ExitCollectSelector(ctx *CollectSelectorContext) {}
|
||||||
|
|
||||||
|
// EnterCollectGrouping is called when production collectGrouping is entered.
|
||||||
|
func (s *BaseFqlParserListener) EnterCollectGrouping(ctx *CollectGroupingContext) {}
|
||||||
|
|
||||||
|
// ExitCollectGrouping is called when production collectGrouping is exited.
|
||||||
|
func (s *BaseFqlParserListener) ExitCollectGrouping(ctx *CollectGroupingContext) {}
|
||||||
|
|
||||||
|
// EnterCollectAggregator is called when production collectAggregator is entered.
|
||||||
|
func (s *BaseFqlParserListener) EnterCollectAggregator(ctx *CollectAggregatorContext) {}
|
||||||
|
|
||||||
|
// ExitCollectAggregator is called when production collectAggregator is exited.
|
||||||
|
func (s *BaseFqlParserListener) ExitCollectAggregator(ctx *CollectAggregatorContext) {}
|
||||||
|
|
||||||
|
// EnterCollectAggregateSelector is called when production collectAggregateSelector is entered.
|
||||||
|
func (s *BaseFqlParserListener) EnterCollectAggregateSelector(ctx *CollectAggregateSelectorContext) {}
|
||||||
|
|
||||||
|
// ExitCollectAggregateSelector is called when production collectAggregateSelector is exited.
|
||||||
|
func (s *BaseFqlParserListener) ExitCollectAggregateSelector(ctx *CollectAggregateSelectorContext) {}
|
||||||
|
|
||||||
// EnterCollectGroupVariable is called when production collectGroupVariable is entered.
|
// EnterCollectGroupVariable is called when production collectGroupVariable is entered.
|
||||||
func (s *BaseFqlParserListener) EnterCollectGroupVariable(ctx *CollectGroupVariableContext) {}
|
func (s *BaseFqlParserListener) EnterCollectGroupVariable(ctx *CollectGroupVariableContext) {}
|
||||||
@ -124,37 +142,11 @@ func (s *BaseFqlParserListener) EnterCollectGroupVariable(ctx *CollectGroupVaria
|
|||||||
// ExitCollectGroupVariable is called when production collectGroupVariable is exited.
|
// ExitCollectGroupVariable is called when production collectGroupVariable is exited.
|
||||||
func (s *BaseFqlParserListener) ExitCollectGroupVariable(ctx *CollectGroupVariableContext) {}
|
func (s *BaseFqlParserListener) ExitCollectGroupVariable(ctx *CollectGroupVariableContext) {}
|
||||||
|
|
||||||
// EnterCollectKeepVariable is called when production collectKeepVariable is entered.
|
// EnterCollectCounter is called when production collectCounter is entered.
|
||||||
func (s *BaseFqlParserListener) EnterCollectKeepVariable(ctx *CollectKeepVariableContext) {}
|
func (s *BaseFqlParserListener) EnterCollectCounter(ctx *CollectCounterContext) {}
|
||||||
|
|
||||||
// ExitCollectKeepVariable is called when production collectKeepVariable is exited.
|
// ExitCollectCounter is called when production collectCounter is exited.
|
||||||
func (s *BaseFqlParserListener) ExitCollectKeepVariable(ctx *CollectKeepVariableContext) {}
|
func (s *BaseFqlParserListener) ExitCollectCounter(ctx *CollectCounterContext) {}
|
||||||
|
|
||||||
// EnterCollectCountVariable is called when production collectCountVariable is entered.
|
|
||||||
func (s *BaseFqlParserListener) EnterCollectCountVariable(ctx *CollectCountVariableContext) {}
|
|
||||||
|
|
||||||
// ExitCollectCountVariable is called when production collectCountVariable is exited.
|
|
||||||
func (s *BaseFqlParserListener) ExitCollectCountVariable(ctx *CollectCountVariableContext) {}
|
|
||||||
|
|
||||||
// EnterCollectAggregateVariable is called when production collectAggregateVariable is entered.
|
|
||||||
func (s *BaseFqlParserListener) EnterCollectAggregateVariable(ctx *CollectAggregateVariableContext) {}
|
|
||||||
|
|
||||||
// ExitCollectAggregateVariable is called when production collectAggregateVariable is exited.
|
|
||||||
func (s *BaseFqlParserListener) ExitCollectAggregateVariable(ctx *CollectAggregateVariableContext) {}
|
|
||||||
|
|
||||||
// EnterCollectAggregateExpression is called when production collectAggregateExpression is entered.
|
|
||||||
func (s *BaseFqlParserListener) EnterCollectAggregateExpression(ctx *CollectAggregateExpressionContext) {
|
|
||||||
}
|
|
||||||
|
|
||||||
// ExitCollectAggregateExpression is called when production collectAggregateExpression is exited.
|
|
||||||
func (s *BaseFqlParserListener) ExitCollectAggregateExpression(ctx *CollectAggregateExpressionContext) {
|
|
||||||
}
|
|
||||||
|
|
||||||
// EnterCollectOption is called when production collectOption is entered.
|
|
||||||
func (s *BaseFqlParserListener) EnterCollectOption(ctx *CollectOptionContext) {}
|
|
||||||
|
|
||||||
// ExitCollectOption is called when production collectOption is exited.
|
|
||||||
func (s *BaseFqlParserListener) ExitCollectOption(ctx *CollectOptionContext) {}
|
|
||||||
|
|
||||||
// EnterForExpressionBody is called when production forExpressionBody is entered.
|
// EnterForExpressionBody is called when production forExpressionBody is entered.
|
||||||
func (s *BaseFqlParserListener) EnterForExpressionBody(ctx *ForExpressionBodyContext) {}
|
func (s *BaseFqlParserListener) EnterForExpressionBody(ctx *ForExpressionBodyContext) {}
|
||||||
|
@ -67,7 +67,19 @@ func (v *BaseFqlParserVisitor) VisitCollectClause(ctx *CollectClauseContext) int
|
|||||||
return v.VisitChildren(ctx)
|
return v.VisitChildren(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *BaseFqlParserVisitor) VisitCollectVariable(ctx *CollectVariableContext) interface{} {
|
func (v *BaseFqlParserVisitor) VisitCollectSelector(ctx *CollectSelectorContext) interface{} {
|
||||||
|
return v.VisitChildren(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *BaseFqlParserVisitor) VisitCollectGrouping(ctx *CollectGroupingContext) interface{} {
|
||||||
|
return v.VisitChildren(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *BaseFqlParserVisitor) VisitCollectAggregator(ctx *CollectAggregatorContext) interface{} {
|
||||||
|
return v.VisitChildren(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *BaseFqlParserVisitor) VisitCollectAggregateSelector(ctx *CollectAggregateSelectorContext) interface{} {
|
||||||
return v.VisitChildren(ctx)
|
return v.VisitChildren(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -75,23 +87,7 @@ func (v *BaseFqlParserVisitor) VisitCollectGroupVariable(ctx *CollectGroupVariab
|
|||||||
return v.VisitChildren(ctx)
|
return v.VisitChildren(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *BaseFqlParserVisitor) VisitCollectKeepVariable(ctx *CollectKeepVariableContext) interface{} {
|
func (v *BaseFqlParserVisitor) VisitCollectCounter(ctx *CollectCounterContext) interface{} {
|
||||||
return v.VisitChildren(ctx)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v *BaseFqlParserVisitor) VisitCollectCountVariable(ctx *CollectCountVariableContext) interface{} {
|
|
||||||
return v.VisitChildren(ctx)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v *BaseFqlParserVisitor) VisitCollectAggregateVariable(ctx *CollectAggregateVariableContext) interface{} {
|
|
||||||
return v.VisitChildren(ctx)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v *BaseFqlParserVisitor) VisitCollectAggregateExpression(ctx *CollectAggregateExpressionContext) interface{} {
|
|
||||||
return v.VisitChildren(ctx)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v *BaseFqlParserVisitor) VisitCollectOption(ctx *CollectOptionContext) interface{} {
|
|
||||||
return v.VisitChildren(ctx)
|
return v.VisitChildren(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -52,26 +52,23 @@ type FqlParserListener interface {
|
|||||||
// EnterCollectClause is called when entering the collectClause production.
|
// EnterCollectClause is called when entering the collectClause production.
|
||||||
EnterCollectClause(c *CollectClauseContext)
|
EnterCollectClause(c *CollectClauseContext)
|
||||||
|
|
||||||
// EnterCollectVariable is called when entering the collectVariable production.
|
// EnterCollectSelector is called when entering the collectSelector production.
|
||||||
EnterCollectVariable(c *CollectVariableContext)
|
EnterCollectSelector(c *CollectSelectorContext)
|
||||||
|
|
||||||
|
// EnterCollectGrouping is called when entering the collectGrouping production.
|
||||||
|
EnterCollectGrouping(c *CollectGroupingContext)
|
||||||
|
|
||||||
|
// EnterCollectAggregator is called when entering the collectAggregator production.
|
||||||
|
EnterCollectAggregator(c *CollectAggregatorContext)
|
||||||
|
|
||||||
|
// EnterCollectAggregateSelector is called when entering the collectAggregateSelector production.
|
||||||
|
EnterCollectAggregateSelector(c *CollectAggregateSelectorContext)
|
||||||
|
|
||||||
// EnterCollectGroupVariable is called when entering the collectGroupVariable production.
|
// EnterCollectGroupVariable is called when entering the collectGroupVariable production.
|
||||||
EnterCollectGroupVariable(c *CollectGroupVariableContext)
|
EnterCollectGroupVariable(c *CollectGroupVariableContext)
|
||||||
|
|
||||||
// EnterCollectKeepVariable is called when entering the collectKeepVariable production.
|
// EnterCollectCounter is called when entering the collectCounter production.
|
||||||
EnterCollectKeepVariable(c *CollectKeepVariableContext)
|
EnterCollectCounter(c *CollectCounterContext)
|
||||||
|
|
||||||
// EnterCollectCountVariable is called when entering the collectCountVariable production.
|
|
||||||
EnterCollectCountVariable(c *CollectCountVariableContext)
|
|
||||||
|
|
||||||
// EnterCollectAggregateVariable is called when entering the collectAggregateVariable production.
|
|
||||||
EnterCollectAggregateVariable(c *CollectAggregateVariableContext)
|
|
||||||
|
|
||||||
// EnterCollectAggregateExpression is called when entering the collectAggregateExpression production.
|
|
||||||
EnterCollectAggregateExpression(c *CollectAggregateExpressionContext)
|
|
||||||
|
|
||||||
// EnterCollectOption is called when entering the collectOption production.
|
|
||||||
EnterCollectOption(c *CollectOptionContext)
|
|
||||||
|
|
||||||
// EnterForExpressionBody is called when entering the forExpressionBody production.
|
// EnterForExpressionBody is called when entering the forExpressionBody production.
|
||||||
EnterForExpressionBody(c *ForExpressionBodyContext)
|
EnterForExpressionBody(c *ForExpressionBodyContext)
|
||||||
@ -208,26 +205,23 @@ type FqlParserListener interface {
|
|||||||
// ExitCollectClause is called when exiting the collectClause production.
|
// ExitCollectClause is called when exiting the collectClause production.
|
||||||
ExitCollectClause(c *CollectClauseContext)
|
ExitCollectClause(c *CollectClauseContext)
|
||||||
|
|
||||||
// ExitCollectVariable is called when exiting the collectVariable production.
|
// ExitCollectSelector is called when exiting the collectSelector production.
|
||||||
ExitCollectVariable(c *CollectVariableContext)
|
ExitCollectSelector(c *CollectSelectorContext)
|
||||||
|
|
||||||
|
// ExitCollectGrouping is called when exiting the collectGrouping production.
|
||||||
|
ExitCollectGrouping(c *CollectGroupingContext)
|
||||||
|
|
||||||
|
// ExitCollectAggregator is called when exiting the collectAggregator production.
|
||||||
|
ExitCollectAggregator(c *CollectAggregatorContext)
|
||||||
|
|
||||||
|
// ExitCollectAggregateSelector is called when exiting the collectAggregateSelector production.
|
||||||
|
ExitCollectAggregateSelector(c *CollectAggregateSelectorContext)
|
||||||
|
|
||||||
// ExitCollectGroupVariable is called when exiting the collectGroupVariable production.
|
// ExitCollectGroupVariable is called when exiting the collectGroupVariable production.
|
||||||
ExitCollectGroupVariable(c *CollectGroupVariableContext)
|
ExitCollectGroupVariable(c *CollectGroupVariableContext)
|
||||||
|
|
||||||
// ExitCollectKeepVariable is called when exiting the collectKeepVariable production.
|
// ExitCollectCounter is called when exiting the collectCounter production.
|
||||||
ExitCollectKeepVariable(c *CollectKeepVariableContext)
|
ExitCollectCounter(c *CollectCounterContext)
|
||||||
|
|
||||||
// ExitCollectCountVariable is called when exiting the collectCountVariable production.
|
|
||||||
ExitCollectCountVariable(c *CollectCountVariableContext)
|
|
||||||
|
|
||||||
// ExitCollectAggregateVariable is called when exiting the collectAggregateVariable production.
|
|
||||||
ExitCollectAggregateVariable(c *CollectAggregateVariableContext)
|
|
||||||
|
|
||||||
// ExitCollectAggregateExpression is called when exiting the collectAggregateExpression production.
|
|
||||||
ExitCollectAggregateExpression(c *CollectAggregateExpressionContext)
|
|
||||||
|
|
||||||
// ExitCollectOption is called when exiting the collectOption production.
|
|
||||||
ExitCollectOption(c *CollectOptionContext)
|
|
||||||
|
|
||||||
// ExitForExpressionBody is called when exiting the forExpressionBody production.
|
// ExitForExpressionBody is called when exiting the forExpressionBody production.
|
||||||
ExitForExpressionBody(c *ForExpressionBodyContext)
|
ExitForExpressionBody(c *ForExpressionBodyContext)
|
||||||
|
@ -52,26 +52,23 @@ type FqlParserVisitor interface {
|
|||||||
// Visit a parse tree produced by FqlParser#collectClause.
|
// Visit a parse tree produced by FqlParser#collectClause.
|
||||||
VisitCollectClause(ctx *CollectClauseContext) interface{}
|
VisitCollectClause(ctx *CollectClauseContext) interface{}
|
||||||
|
|
||||||
// Visit a parse tree produced by FqlParser#collectVariable.
|
// Visit a parse tree produced by FqlParser#collectSelector.
|
||||||
VisitCollectVariable(ctx *CollectVariableContext) interface{}
|
VisitCollectSelector(ctx *CollectSelectorContext) interface{}
|
||||||
|
|
||||||
|
// Visit a parse tree produced by FqlParser#collectGrouping.
|
||||||
|
VisitCollectGrouping(ctx *CollectGroupingContext) interface{}
|
||||||
|
|
||||||
|
// Visit a parse tree produced by FqlParser#collectAggregator.
|
||||||
|
VisitCollectAggregator(ctx *CollectAggregatorContext) interface{}
|
||||||
|
|
||||||
|
// Visit a parse tree produced by FqlParser#collectAggregateSelector.
|
||||||
|
VisitCollectAggregateSelector(ctx *CollectAggregateSelectorContext) interface{}
|
||||||
|
|
||||||
// Visit a parse tree produced by FqlParser#collectGroupVariable.
|
// Visit a parse tree produced by FqlParser#collectGroupVariable.
|
||||||
VisitCollectGroupVariable(ctx *CollectGroupVariableContext) interface{}
|
VisitCollectGroupVariable(ctx *CollectGroupVariableContext) interface{}
|
||||||
|
|
||||||
// Visit a parse tree produced by FqlParser#collectKeepVariable.
|
// Visit a parse tree produced by FqlParser#collectCounter.
|
||||||
VisitCollectKeepVariable(ctx *CollectKeepVariableContext) interface{}
|
VisitCollectCounter(ctx *CollectCounterContext) interface{}
|
||||||
|
|
||||||
// Visit a parse tree produced by FqlParser#collectCountVariable.
|
|
||||||
VisitCollectCountVariable(ctx *CollectCountVariableContext) interface{}
|
|
||||||
|
|
||||||
// Visit a parse tree produced by FqlParser#collectAggregateVariable.
|
|
||||||
VisitCollectAggregateVariable(ctx *CollectAggregateVariableContext) interface{}
|
|
||||||
|
|
||||||
// Visit a parse tree produced by FqlParser#collectAggregateExpression.
|
|
||||||
VisitCollectAggregateExpression(ctx *CollectAggregateExpressionContext) interface{}
|
|
||||||
|
|
||||||
// Visit a parse tree produced by FqlParser#collectOption.
|
|
||||||
VisitCollectOption(ctx *CollectOptionContext) interface{}
|
|
||||||
|
|
||||||
// Visit a parse tree produced by FqlParser#forExpressionBody.
|
// Visit a parse tree produced by FqlParser#forExpressionBody.
|
||||||
VisitForExpressionBody(ctx *ForExpressionBodyContext) interface{}
|
VisitForExpressionBody(ctx *ForExpressionBodyContext) interface{}
|
||||||
|
@ -1,7 +1,25 @@
|
|||||||
package collections
|
package collections
|
||||||
|
|
||||||
import "github.com/MontFerret/ferret/pkg/runtime/values"
|
import (
|
||||||
|
"github.com/MontFerret/ferret/pkg/runtime/core"
|
||||||
|
"github.com/MontFerret/ferret/pkg/runtime/values"
|
||||||
|
)
|
||||||
|
|
||||||
type Collection interface {
|
type (
|
||||||
|
Collection interface {
|
||||||
Length() values.Int
|
Length() values.Int
|
||||||
}
|
}
|
||||||
|
|
||||||
|
IndexedCollection interface {
|
||||||
|
Collection
|
||||||
|
Get(idx values.Int) core.Value
|
||||||
|
Set(idx values.Int, value core.Value) error
|
||||||
|
}
|
||||||
|
|
||||||
|
KeyedCollection interface {
|
||||||
|
Collection
|
||||||
|
Keys() []string
|
||||||
|
Get(key values.String) (core.Value, values.Boolean)
|
||||||
|
Set(key values.String, value core.Value)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
108
pkg/runtime/collections/data_set.go
Normal file
108
pkg/runtime/collections/data_set.go
Normal file
@ -0,0 +1,108 @@
|
|||||||
|
package collections
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"github.com/MontFerret/ferret/pkg/runtime/core"
|
||||||
|
"github.com/MontFerret/ferret/pkg/runtime/values"
|
||||||
|
"hash/fnv"
|
||||||
|
"sort"
|
||||||
|
)
|
||||||
|
|
||||||
|
type DataSet map[string]core.Value
|
||||||
|
|
||||||
|
func NewDataSet() DataSet {
|
||||||
|
return make(DataSet)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ds DataSet) Apply(scope *core.Scope, variables Variables) error {
|
||||||
|
if err := ValidateDataSet(ds, variables); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, variable := range variables {
|
||||||
|
if variable != "" {
|
||||||
|
value, found := ds[variable]
|
||||||
|
|
||||||
|
if !found {
|
||||||
|
return core.Errorf(core.ErrNotFound, "variable not found in a given data set: %s", variable)
|
||||||
|
}
|
||||||
|
|
||||||
|
scope.SetVariable(variable, value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ds DataSet) Set(key string, value core.Value) {
|
||||||
|
ds[key] = value
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ds DataSet) Get(key string) core.Value {
|
||||||
|
val, found := ds[key]
|
||||||
|
|
||||||
|
if found {
|
||||||
|
return val
|
||||||
|
}
|
||||||
|
|
||||||
|
return values.None
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ds DataSet) Hash() uint64 {
|
||||||
|
h := fnv.New64a()
|
||||||
|
|
||||||
|
keys := make([]string, 0, len(ds))
|
||||||
|
|
||||||
|
for key := range ds {
|
||||||
|
keys = append(keys, key)
|
||||||
|
}
|
||||||
|
|
||||||
|
// order does not really matter
|
||||||
|
// but it will give us a consistent hash sum
|
||||||
|
sort.Strings(keys)
|
||||||
|
endIndex := len(keys) - 1
|
||||||
|
|
||||||
|
h.Write([]byte("{"))
|
||||||
|
|
||||||
|
for idx, key := range keys {
|
||||||
|
h.Write([]byte(key))
|
||||||
|
h.Write([]byte(":"))
|
||||||
|
|
||||||
|
el := ds[key]
|
||||||
|
|
||||||
|
bytes := make([]byte, 8)
|
||||||
|
binary.LittleEndian.PutUint64(bytes, el.Hash())
|
||||||
|
|
||||||
|
h.Write(bytes)
|
||||||
|
|
||||||
|
if idx != endIndex {
|
||||||
|
h.Write([]byte(","))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
h.Write([]byte("}"))
|
||||||
|
|
||||||
|
return h.Sum64()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ds DataSet) Compare(other DataSet) int {
|
||||||
|
if len(ds) > len(ds) {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(ds) < len(ds) {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
|
||||||
|
var res = 0
|
||||||
|
|
||||||
|
for key, otherVal := range other {
|
||||||
|
res = -1
|
||||||
|
|
||||||
|
if val, exists := ds[key]; exists {
|
||||||
|
res = val.Compare(otherVal)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return res
|
||||||
|
}
|
@ -6,4 +6,13 @@ import (
|
|||||||
|
|
||||||
var (
|
var (
|
||||||
ErrExhausted = core.Error(core.ErrInvalidOperation, "iterator has been exhausted")
|
ErrExhausted = core.Error(core.ErrInvalidOperation, "iterator has been exhausted")
|
||||||
|
ErrResultSetMismatch = core.Error(core.ErrInvalidArgument, "count of values in result set is less that count of variables")
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func ValidateDataSet(set DataSet, variables Variables) error {
|
||||||
|
if len(variables) > len(set) {
|
||||||
|
return ErrResultSetMismatch
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
@ -2,17 +2,16 @@ package collections
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/MontFerret/ferret/pkg/runtime/core"
|
"github.com/MontFerret/ferret/pkg/runtime/core"
|
||||||
"github.com/MontFerret/ferret/pkg/runtime/values"
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
type (
|
type (
|
||||||
FilterPredicate func(val core.Value, key core.Value) (bool, error)
|
FilterPredicate func(set DataSet) (bool, error)
|
||||||
|
|
||||||
FilterIterator struct {
|
FilterIterator struct {
|
||||||
src Iterator
|
src Iterator
|
||||||
predicate FilterPredicate
|
predicate FilterPredicate
|
||||||
value core.Value
|
dataSet DataSet
|
||||||
key core.Value
|
|
||||||
ready bool
|
ready bool
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@ -35,34 +34,33 @@ func (iterator *FilterIterator) HasNext() bool {
|
|||||||
iterator.ready = true
|
iterator.ready = true
|
||||||
}
|
}
|
||||||
|
|
||||||
return iterator.value != nil && iterator.value.Type() != core.NoneType
|
return iterator.dataSet != nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (iterator *FilterIterator) Next() (core.Value, core.Value, error) {
|
func (iterator *FilterIterator) Next() (DataSet, error) {
|
||||||
if iterator.HasNext() == true {
|
if iterator.HasNext() == true {
|
||||||
val := iterator.value
|
ds := iterator.dataSet
|
||||||
key := iterator.key
|
|
||||||
|
|
||||||
iterator.filter()
|
iterator.filter()
|
||||||
|
|
||||||
return val, key, nil
|
return ds, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return values.None, values.None, ErrExhausted
|
return nil, ErrExhausted
|
||||||
}
|
}
|
||||||
|
|
||||||
func (iterator *FilterIterator) filter() {
|
func (iterator *FilterIterator) filter() {
|
||||||
var doNext bool
|
var doNext bool
|
||||||
|
|
||||||
for iterator.src.HasNext() {
|
for iterator.src.HasNext() {
|
||||||
val, key, err := iterator.src.Next()
|
set, err := iterator.src.Next()
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
doNext = false
|
doNext = false
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
take, err := iterator.predicate(val, key)
|
take, err := iterator.predicate(set)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
doNext = false
|
doNext = false
|
||||||
@ -71,14 +69,12 @@ func (iterator *FilterIterator) filter() {
|
|||||||
|
|
||||||
if take == true {
|
if take == true {
|
||||||
doNext = true
|
doNext = true
|
||||||
iterator.value = val
|
iterator.dataSet = set
|
||||||
iterator.key = key
|
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if doNext == false {
|
if doNext == false {
|
||||||
iterator.value = nil
|
iterator.dataSet = nil
|
||||||
iterator.key = nil
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -20,15 +20,15 @@ func TestFilter(t *testing.T) {
|
|||||||
values.NewInt(5),
|
values.NewInt(5),
|
||||||
}
|
}
|
||||||
|
|
||||||
predicate := func(val core.Value, _ core.Value) (bool, error) {
|
predicate := func(ds collections.DataSet) (bool, error) {
|
||||||
i := float64(val.Unwrap().(int))
|
i := float64(ds.Get(collections.DefaultValueVar).Unwrap().(int))
|
||||||
calc := float64(i / 2)
|
calc := float64(i / 2)
|
||||||
|
|
||||||
return calc == math.Floor(calc), nil
|
return calc == math.Floor(calc), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
iter, err := collections.NewFilterIterator(
|
iter, err := collections.NewFilterIterator(
|
||||||
collections.NewSliceIterator(arr),
|
sliceIterator(arr),
|
||||||
predicate,
|
predicate,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -37,7 +37,7 @@ func TestFilter(t *testing.T) {
|
|||||||
res := make([]core.Value, 0, len(arr))
|
res := make([]core.Value, 0, len(arr))
|
||||||
|
|
||||||
for iter.HasNext() {
|
for iter.HasNext() {
|
||||||
item, _, err := iter.Next()
|
item, _, err := next(iter)
|
||||||
|
|
||||||
So(err, ShouldBeNil)
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
@ -47,7 +47,7 @@ func TestFilter(t *testing.T) {
|
|||||||
So(res, ShouldHaveLength, 2)
|
So(res, ShouldHaveLength, 2)
|
||||||
})
|
})
|
||||||
|
|
||||||
Convey("Should filter out non-even keys", t, func() {
|
Convey("Should filter out non-even groupKeys", t, func() {
|
||||||
arr := []core.Value{
|
arr := []core.Value{
|
||||||
values.NewInt(1),
|
values.NewInt(1),
|
||||||
values.NewInt(2),
|
values.NewInt(2),
|
||||||
@ -56,8 +56,8 @@ func TestFilter(t *testing.T) {
|
|||||||
values.NewInt(5),
|
values.NewInt(5),
|
||||||
}
|
}
|
||||||
|
|
||||||
predicate := func(_ core.Value, key core.Value) (bool, error) {
|
predicate := func(ds collections.DataSet) (bool, error) {
|
||||||
i := float64(key.Unwrap().(int))
|
i := float64(ds.Get(collections.DefaultKeyVar).Unwrap().(int))
|
||||||
|
|
||||||
if i == 0 {
|
if i == 0 {
|
||||||
return false, nil
|
return false, nil
|
||||||
@ -69,7 +69,7 @@ func TestFilter(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
iter, err := collections.NewFilterIterator(
|
iter, err := collections.NewFilterIterator(
|
||||||
collections.NewSliceIterator(arr),
|
sliceIterator(arr),
|
||||||
predicate,
|
predicate,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -78,7 +78,7 @@ func TestFilter(t *testing.T) {
|
|||||||
res := make([]core.Value, 0, len(arr))
|
res := make([]core.Value, 0, len(arr))
|
||||||
|
|
||||||
for iter.HasNext() {
|
for iter.HasNext() {
|
||||||
item, _, err := iter.Next()
|
item, _, err := next(iter)
|
||||||
|
|
||||||
So(err, ShouldBeNil)
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
@ -97,12 +97,12 @@ func TestFilter(t *testing.T) {
|
|||||||
values.NewInt(5),
|
values.NewInt(5),
|
||||||
}
|
}
|
||||||
|
|
||||||
predicate := func(val core.Value, _ core.Value) (bool, error) {
|
predicate := func(_ collections.DataSet) (bool, error) {
|
||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
iter, err := collections.NewFilterIterator(
|
iter, err := collections.NewFilterIterator(
|
||||||
collections.NewSliceIterator(arr),
|
sliceIterator(arr),
|
||||||
predicate,
|
predicate,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -111,7 +111,7 @@ func TestFilter(t *testing.T) {
|
|||||||
res := make([]core.Value, 0, len(arr))
|
res := make([]core.Value, 0, len(arr))
|
||||||
|
|
||||||
for iter.HasNext() {
|
for iter.HasNext() {
|
||||||
item, _, err := iter.Next()
|
item, _, err := next(iter)
|
||||||
|
|
||||||
So(err, ShouldBeNil)
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
@ -130,12 +130,12 @@ func TestFilter(t *testing.T) {
|
|||||||
values.NewInt(5),
|
values.NewInt(5),
|
||||||
}
|
}
|
||||||
|
|
||||||
predicate := func(val core.Value, _ core.Value) (bool, error) {
|
predicate := func(_ collections.DataSet) (bool, error) {
|
||||||
return true, nil
|
return true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
iter, err := collections.NewFilterIterator(
|
iter, err := collections.NewFilterIterator(
|
||||||
collections.NewSliceIterator(arr),
|
sliceIterator(arr),
|
||||||
predicate,
|
predicate,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -144,7 +144,7 @@ func TestFilter(t *testing.T) {
|
|||||||
res := make([]core.Value, 0, len(arr))
|
res := make([]core.Value, 0, len(arr))
|
||||||
|
|
||||||
for iter.HasNext() {
|
for iter.HasNext() {
|
||||||
item, _, err := iter.Next()
|
item, _, err := next(iter)
|
||||||
|
|
||||||
So(err, ShouldBeNil)
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
@ -163,12 +163,12 @@ func TestFilter(t *testing.T) {
|
|||||||
values.NewInt(5),
|
values.NewInt(5),
|
||||||
}
|
}
|
||||||
|
|
||||||
predicate := func(val core.Value, _ core.Value) (bool, error) {
|
predicate := func(_ collections.DataSet) (bool, error) {
|
||||||
return true, nil
|
return true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
iter, err := collections.NewFilterIterator(
|
iter, err := collections.NewFilterIterator(
|
||||||
collections.NewSliceIterator(arr),
|
sliceIterator(arr),
|
||||||
predicate,
|
predicate,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -177,15 +177,16 @@ func TestFilter(t *testing.T) {
|
|||||||
res := make([]core.Value, 0, len(arr))
|
res := make([]core.Value, 0, len(arr))
|
||||||
|
|
||||||
for iter.HasNext() {
|
for iter.HasNext() {
|
||||||
item, _, err := iter.Next()
|
item, _, err := next(iter)
|
||||||
|
|
||||||
So(err, ShouldBeNil)
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
res = append(res, item)
|
res = append(res, item)
|
||||||
}
|
}
|
||||||
|
|
||||||
_, _, err = iter.Next()
|
item, _, err := next(iter)
|
||||||
|
|
||||||
|
So(item, ShouldBeNil)
|
||||||
So(err, ShouldBeError)
|
So(err, ShouldBeError)
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -199,17 +200,17 @@ func TestFilter(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// i < 5
|
// i < 5
|
||||||
predicate1 := func(val core.Value, _ core.Value) (bool, error) {
|
predicate1 := func(ds collections.DataSet) (bool, error) {
|
||||||
return val.Compare(values.NewInt(5)) == -1, nil
|
return ds.Get(collections.DefaultValueVar).Compare(values.NewInt(5)) == -1, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// i > 2
|
// i > 2
|
||||||
predicate2 := func(val core.Value, _ core.Value) (bool, error) {
|
predicate2 := func(ds collections.DataSet) (bool, error) {
|
||||||
return val.Compare(values.NewInt(2)) == 1, nil
|
return ds.Get(collections.DefaultValueVar).Compare(values.NewInt(2)) == 1, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
it, _ := collections.NewFilterIterator(
|
it, _ := collections.NewFilterIterator(
|
||||||
collections.NewSliceIterator(arr),
|
sliceIterator(arr),
|
||||||
predicate1,
|
predicate1,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -220,10 +221,12 @@ func TestFilter(t *testing.T) {
|
|||||||
|
|
||||||
So(err, ShouldBeNil)
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
res, err := collections.ToSlice(iter)
|
sets, err := collections.ToSlice(iter)
|
||||||
|
|
||||||
So(err, ShouldBeNil)
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
res := toArrayOfValues(sets)
|
||||||
|
|
||||||
js, _ := json.Marshal(res)
|
js, _ := json.Marshal(res)
|
||||||
|
|
||||||
So(string(js), ShouldEqual, `[3,4]`)
|
So(string(js), ShouldEqual, `[3,4]`)
|
||||||
|
@ -1,85 +0,0 @@
|
|||||||
package collections
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/MontFerret/ferret/pkg/runtime/core"
|
|
||||||
"github.com/MontFerret/ferret/pkg/runtime/values"
|
|
||||||
)
|
|
||||||
|
|
||||||
type (
|
|
||||||
GroupKey func(value core.Value) (core.Value, error)
|
|
||||||
GroupIterator struct {
|
|
||||||
src Iterator
|
|
||||||
keys []GroupKey
|
|
||||||
ready bool
|
|
||||||
values *MapIterator
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
func NewGroupIterator(
|
|
||||||
src Iterator,
|
|
||||||
keys ...GroupKey,
|
|
||||||
) (*GroupIterator, error) {
|
|
||||||
if core.IsNil(src) {
|
|
||||||
return nil, core.Error(core.ErrMissedArgument, "source")
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(keys) == 0 {
|
|
||||||
return nil, core.Error(core.ErrMissedArgument, "key(s)")
|
|
||||||
}
|
|
||||||
|
|
||||||
return &GroupIterator{src, keys, false, nil}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (iterator *GroupIterator) HasNext() bool {
|
|
||||||
if !iterator.ready {
|
|
||||||
iterator.ready = true
|
|
||||||
groups, err := iterator.group()
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
iterator.values = NewMapIterator(map[string]core.Value{})
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
iterator.values = groups
|
|
||||||
}
|
|
||||||
|
|
||||||
return iterator.values.HasNext()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (iterator *GroupIterator) Next() (core.Value, core.Value, error) {
|
|
||||||
return iterator.values.Next()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (iterator *GroupIterator) group() (*MapIterator, error) {
|
|
||||||
groups := make(map[string]core.Value)
|
|
||||||
|
|
||||||
for iterator.src.HasNext() {
|
|
||||||
for _, keyFn := range iterator.keys {
|
|
||||||
val, _, err := iterator.src.Next()
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
keyVal, err := keyFn(val)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
key := keyVal.String()
|
|
||||||
|
|
||||||
group, exists := groups[key]
|
|
||||||
|
|
||||||
if !exists {
|
|
||||||
group = values.NewArray(10)
|
|
||||||
groups[key] = group
|
|
||||||
}
|
|
||||||
|
|
||||||
group.(*values.Array).Push(val)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return NewMapIterator(groups), nil
|
|
||||||
}
|
|
@ -1,53 +0,0 @@
|
|||||||
package collections_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"github.com/MontFerret/ferret/pkg/runtime/collections"
|
|
||||||
"github.com/MontFerret/ferret/pkg/runtime/core"
|
|
||||||
"github.com/MontFerret/ferret/pkg/runtime/values"
|
|
||||||
. "github.com/smartystreets/goconvey/convey"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestGroup(t *testing.T) {
|
|
||||||
makeObj := func(active bool, age int, city, gender string) *values.Object {
|
|
||||||
obj := values.NewObject()
|
|
||||||
|
|
||||||
obj.Set("active", values.NewBoolean(active))
|
|
||||||
obj.Set("age", values.NewInt(age))
|
|
||||||
obj.Set("city", values.NewString(city))
|
|
||||||
obj.Set("gender", values.NewString(gender))
|
|
||||||
|
|
||||||
return obj
|
|
||||||
}
|
|
||||||
|
|
||||||
Convey("Should group by a single key", t, func() {
|
|
||||||
arr := []core.Value{
|
|
||||||
makeObj(true, 31, "D.C.", "m"),
|
|
||||||
makeObj(true, 29, "L.A.", "f"),
|
|
||||||
makeObj(true, 36, "D.C.", "m"),
|
|
||||||
makeObj(true, 34, "N.Y.C.", "f"),
|
|
||||||
makeObj(true, 28, "L.A.", "f"),
|
|
||||||
makeObj(true, 41, "Boston", "m"),
|
|
||||||
}
|
|
||||||
|
|
||||||
iter, err := collections.NewGroupIterator(
|
|
||||||
collections.NewSliceIterator(arr),
|
|
||||||
func(value core.Value) (core.Value, error) {
|
|
||||||
val, _ := value.(*values.Object).Get("gender")
|
|
||||||
|
|
||||||
return val, nil
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
So(err, ShouldBeNil)
|
|
||||||
|
|
||||||
res, err := collections.ToMap(iter)
|
|
||||||
|
|
||||||
So(err, ShouldBeNil)
|
|
||||||
|
|
||||||
j, _ := json.Marshal(res)
|
|
||||||
|
|
||||||
So(string(j), ShouldEqual, `{"f":[{"active":true,"age":29,"city":"L.A.","gender":"f"},{"active":true,"age":34,"city":"N.Y.C.","gender":"f"},{"active":true,"age":28,"city":"L.A.","gender":"f"}],"m":[{"active":true,"age":31,"city":"D.C.","gender":"m"},{"active":true,"age":36,"city":"D.C.","gender":"m"},{"active":true,"age":41,"city":"Boston","gender":"m"}]}`)
|
|
||||||
})
|
|
||||||
}
|
|
@ -1,25 +0,0 @@
|
|||||||
package collections
|
|
||||||
|
|
||||||
import "github.com/MontFerret/ferret/pkg/runtime/core"
|
|
||||||
|
|
||||||
func ToHashTable(iterator Iterator) (map[uint64]core.Value, error) {
|
|
||||||
result := make(map[uint64]core.Value)
|
|
||||||
|
|
||||||
for iterator.HasNext() {
|
|
||||||
val, _, err := iterator.Next()
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
h := val.Hash()
|
|
||||||
|
|
||||||
_, exists := result[h]
|
|
||||||
|
|
||||||
if !exists {
|
|
||||||
result[h] = val
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return result, nil
|
|
||||||
}
|
|
40
pkg/runtime/collections/html.go
Normal file
40
pkg/runtime/collections/html.go
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
package collections
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/MontFerret/ferret/pkg/runtime/values"
|
||||||
|
)
|
||||||
|
|
||||||
|
type HTMLNodeIterator struct {
|
||||||
|
valVar string
|
||||||
|
keyVar string
|
||||||
|
values values.HTMLNode
|
||||||
|
pos int
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewHTMLNodeIterator(
|
||||||
|
valVar,
|
||||||
|
keyVar string,
|
||||||
|
input values.HTMLNode,
|
||||||
|
) Iterator {
|
||||||
|
return &HTMLNodeIterator{valVar, keyVar, input, 0}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (iterator *HTMLNodeIterator) HasNext() bool {
|
||||||
|
return iterator.values.Length() > values.NewInt(iterator.pos)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (iterator *HTMLNodeIterator) Next() (DataSet, error) {
|
||||||
|
if iterator.values.Length() > values.NewInt(iterator.pos) {
|
||||||
|
idx := values.NewInt(iterator.pos)
|
||||||
|
val := iterator.values.GetChildNode(idx)
|
||||||
|
|
||||||
|
iterator.pos++
|
||||||
|
|
||||||
|
return DataSet{
|
||||||
|
iterator.valVar: val,
|
||||||
|
iterator.keyVar: idx,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, ErrExhausted
|
||||||
|
}
|
50
pkg/runtime/collections/indexed.go
Normal file
50
pkg/runtime/collections/indexed.go
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
package collections
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/MontFerret/ferret/pkg/runtime/values"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
DefaultValueVar = "value"
|
||||||
|
DefaultKeyVar = "key"
|
||||||
|
)
|
||||||
|
|
||||||
|
type IndexedIterator struct {
|
||||||
|
valVar string
|
||||||
|
keyVar string
|
||||||
|
values IndexedCollection
|
||||||
|
pos int
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewIndexedIterator(
|
||||||
|
valVar,
|
||||||
|
keyVar string,
|
||||||
|
input IndexedCollection,
|
||||||
|
) Iterator {
|
||||||
|
return &IndexedIterator{valVar, keyVar, input, 0}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewDefaultIndexedIterator(
|
||||||
|
input IndexedCollection,
|
||||||
|
) Iterator {
|
||||||
|
return &IndexedIterator{DefaultValueVar, DefaultKeyVar, input, 0}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (iterator *IndexedIterator) HasNext() bool {
|
||||||
|
return int(iterator.values.Length()) > iterator.pos
|
||||||
|
}
|
||||||
|
|
||||||
|
func (iterator *IndexedIterator) Next() (DataSet, error) {
|
||||||
|
if int(iterator.values.Length()) > iterator.pos {
|
||||||
|
idx := values.NewInt(iterator.pos)
|
||||||
|
val := iterator.values.Get(idx)
|
||||||
|
iterator.pos++
|
||||||
|
|
||||||
|
return DataSet{
|
||||||
|
iterator.valVar: val,
|
||||||
|
iterator.keyVar: idx,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, ErrExhausted
|
||||||
|
}
|
129
pkg/runtime/collections/indexed_test.go
Normal file
129
pkg/runtime/collections/indexed_test.go
Normal file
@ -0,0 +1,129 @@
|
|||||||
|
package collections_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/MontFerret/ferret/pkg/runtime/collections"
|
||||||
|
"github.com/MontFerret/ferret/pkg/runtime/core"
|
||||||
|
"github.com/MontFerret/ferret/pkg/runtime/values"
|
||||||
|
. "github.com/smartystreets/goconvey/convey"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func next(iterator collections.Iterator) (core.Value, core.Value, error) {
|
||||||
|
ds, err := iterator.Next()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
val := ds[collections.DefaultValueVar]
|
||||||
|
key := ds[collections.DefaultKeyVar]
|
||||||
|
|
||||||
|
return val, key, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func arrayIterator(arr *values.Array) collections.Iterator {
|
||||||
|
return collections.NewDefaultIndexedIterator(arr)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestArrayIterator(t *testing.T) {
|
||||||
|
|
||||||
|
Convey("Should iterate over an array", t, func() {
|
||||||
|
arr := values.NewArrayWith(
|
||||||
|
values.NewInt(1),
|
||||||
|
values.NewInt(2),
|
||||||
|
values.NewInt(3),
|
||||||
|
values.NewInt(4),
|
||||||
|
values.NewInt(5),
|
||||||
|
)
|
||||||
|
|
||||||
|
iter := arrayIterator(arr)
|
||||||
|
|
||||||
|
res := make([]core.Value, 0, arr.Length())
|
||||||
|
|
||||||
|
pos := 0
|
||||||
|
|
||||||
|
for iter.HasNext() {
|
||||||
|
item, key, err := next(iter)
|
||||||
|
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(key.Unwrap(), ShouldEqual, pos)
|
||||||
|
|
||||||
|
res = append(res, item)
|
||||||
|
|
||||||
|
pos += 1
|
||||||
|
}
|
||||||
|
|
||||||
|
So(res, ShouldHaveLength, arr.Length())
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("Should iterate over an array in the same order", t, func() {
|
||||||
|
arr := values.NewArrayWith(
|
||||||
|
values.NewInt(1),
|
||||||
|
values.NewInt(2),
|
||||||
|
values.NewInt(3),
|
||||||
|
values.NewInt(4),
|
||||||
|
values.NewInt(5),
|
||||||
|
)
|
||||||
|
|
||||||
|
iter := arrayIterator(arr)
|
||||||
|
|
||||||
|
res := make([]core.Value, 0, arr.Length())
|
||||||
|
|
||||||
|
for iter.HasNext() {
|
||||||
|
item, _, err := next(iter)
|
||||||
|
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
res = append(res, item)
|
||||||
|
}
|
||||||
|
|
||||||
|
arr.ForEach(func(expected core.Value, idx int) bool {
|
||||||
|
actual := res[idx]
|
||||||
|
|
||||||
|
So(actual, ShouldEqual, expected)
|
||||||
|
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("Should return an error when exhausted", t, func() {
|
||||||
|
arr := values.NewArrayWith(
|
||||||
|
values.NewInt(1),
|
||||||
|
values.NewInt(2),
|
||||||
|
values.NewInt(3),
|
||||||
|
values.NewInt(4),
|
||||||
|
values.NewInt(5),
|
||||||
|
)
|
||||||
|
|
||||||
|
iter := arrayIterator(arr)
|
||||||
|
|
||||||
|
res := make([]core.Value, 0, arr.Length())
|
||||||
|
|
||||||
|
for iter.HasNext() {
|
||||||
|
item, _, err := next(iter)
|
||||||
|
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
res = append(res, item)
|
||||||
|
}
|
||||||
|
|
||||||
|
item, _, err := next(iter)
|
||||||
|
|
||||||
|
So(item, ShouldBeNil)
|
||||||
|
So(err, ShouldBeError)
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("Should NOT iterate over an empty array", t, func() {
|
||||||
|
arr := values.NewArray(10)
|
||||||
|
|
||||||
|
iter := arrayIterator(arr)
|
||||||
|
|
||||||
|
var iterated bool
|
||||||
|
|
||||||
|
for iter.HasNext() {
|
||||||
|
iterated = true
|
||||||
|
}
|
||||||
|
|
||||||
|
So(iterated, ShouldBeFalse)
|
||||||
|
})
|
||||||
|
}
|
@ -3,234 +3,34 @@ package collections
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"github.com/MontFerret/ferret/pkg/runtime/core"
|
"github.com/MontFerret/ferret/pkg/runtime/core"
|
||||||
"github.com/MontFerret/ferret/pkg/runtime/values"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type (
|
type (
|
||||||
|
Variables []string
|
||||||
|
|
||||||
Iterator interface {
|
Iterator interface {
|
||||||
HasNext() bool
|
HasNext() bool
|
||||||
Next() (value core.Value, key core.Value, err error)
|
Next() (DataSet, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
Iterable interface {
|
Iterable interface {
|
||||||
Iterate() Iterator
|
Variables() Variables
|
||||||
}
|
|
||||||
|
|
||||||
IterableExpression interface {
|
|
||||||
core.Expression
|
|
||||||
Iterate(ctx context.Context, scope *core.Scope) (Iterator, error)
|
Iterate(ctx context.Context, scope *core.Scope) (Iterator, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
SliceIterator struct {
|
|
||||||
values []core.Value
|
|
||||||
pos int
|
|
||||||
}
|
|
||||||
|
|
||||||
MapIterator struct {
|
|
||||||
values map[string]core.Value
|
|
||||||
keys []string
|
|
||||||
pos int
|
|
||||||
}
|
|
||||||
|
|
||||||
ArrayIterator struct {
|
|
||||||
values *values.Array
|
|
||||||
pos int
|
|
||||||
}
|
|
||||||
|
|
||||||
ObjectIterator struct {
|
|
||||||
values *values.Object
|
|
||||||
keys []string
|
|
||||||
pos int
|
|
||||||
}
|
|
||||||
|
|
||||||
HTMLNodeIterator struct {
|
|
||||||
values values.HTMLNode
|
|
||||||
pos int
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func ToIterator(value core.Value) (Iterator, error) {
|
func ToSlice(iterator Iterator) ([]DataSet, error) {
|
||||||
switch value.Type() {
|
res := make([]DataSet, 0, 10)
|
||||||
case core.ArrayType:
|
|
||||||
return NewArrayIterator(value.(*values.Array)), nil
|
|
||||||
case core.ObjectType:
|
|
||||||
return NewObjectIterator(value.(*values.Object)), nil
|
|
||||||
case core.HTMLElementType, core.HTMLDocumentType:
|
|
||||||
return NewHTMLNodeIterator(value.(values.HTMLNode)), nil
|
|
||||||
default:
|
|
||||||
return nil, core.TypeError(
|
|
||||||
value.Type(),
|
|
||||||
core.ArrayType,
|
|
||||||
core.ObjectType,
|
|
||||||
core.HTMLDocumentType,
|
|
||||||
core.HTMLElementType,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func ToSlice(iterator Iterator) ([]core.Value, error) {
|
|
||||||
res := make([]core.Value, 0, 10)
|
|
||||||
|
|
||||||
for iterator.HasNext() {
|
for iterator.HasNext() {
|
||||||
item, _, err := iterator.Next()
|
ds, err := iterator.Next()
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
res = append(res, item)
|
res = append(res, ds)
|
||||||
}
|
}
|
||||||
|
|
||||||
return res, nil
|
return res, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func ToMap(iterator Iterator) (map[string]core.Value, error) {
|
|
||||||
res := make(map[string]core.Value)
|
|
||||||
|
|
||||||
for iterator.HasNext() {
|
|
||||||
item, key, err := iterator.Next()
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
res[key.String()] = item
|
|
||||||
}
|
|
||||||
|
|
||||||
return res, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func ToArray(iterator Iterator) (*values.Array, error) {
|
|
||||||
res := values.NewArray(10)
|
|
||||||
|
|
||||||
for iterator.HasNext() {
|
|
||||||
item, _, err := iterator.Next()
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
res.Push(item)
|
|
||||||
}
|
|
||||||
|
|
||||||
return res, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewSliceIterator(input []core.Value) *SliceIterator {
|
|
||||||
return &SliceIterator{input, 0}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (iterator *SliceIterator) HasNext() bool {
|
|
||||||
return len(iterator.values) > iterator.pos
|
|
||||||
}
|
|
||||||
|
|
||||||
func (iterator *SliceIterator) Next() (core.Value, core.Value, error) {
|
|
||||||
if len(iterator.values) > iterator.pos {
|
|
||||||
idx := iterator.pos
|
|
||||||
val := iterator.values[idx]
|
|
||||||
iterator.pos++
|
|
||||||
|
|
||||||
return val, values.NewInt(idx), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return values.None, values.None, ErrExhausted
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewMapIterator(input map[string]core.Value) *MapIterator {
|
|
||||||
return &MapIterator{input, nil, 0}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (iterator *MapIterator) HasNext() bool {
|
|
||||||
// lazy initialization
|
|
||||||
if iterator.keys == nil {
|
|
||||||
keys := make([]string, len(iterator.values))
|
|
||||||
|
|
||||||
i := 0
|
|
||||||
for k := range iterator.values {
|
|
||||||
keys[i] = k
|
|
||||||
i++
|
|
||||||
}
|
|
||||||
|
|
||||||
iterator.keys = keys
|
|
||||||
}
|
|
||||||
|
|
||||||
return len(iterator.keys) > iterator.pos
|
|
||||||
}
|
|
||||||
|
|
||||||
func (iterator *MapIterator) Next() (core.Value, core.Value, error) {
|
|
||||||
if len(iterator.keys) > iterator.pos {
|
|
||||||
key := iterator.keys[iterator.pos]
|
|
||||||
val := iterator.values[key]
|
|
||||||
iterator.pos++
|
|
||||||
|
|
||||||
return val, values.NewString(key), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return values.None, values.None, ErrExhausted
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewArrayIterator(input *values.Array) *ArrayIterator {
|
|
||||||
return &ArrayIterator{input, 0}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (iterator *ArrayIterator) HasNext() bool {
|
|
||||||
return int(iterator.values.Length()) > iterator.pos
|
|
||||||
}
|
|
||||||
|
|
||||||
func (iterator *ArrayIterator) Next() (core.Value, core.Value, error) {
|
|
||||||
if int(iterator.values.Length()) > iterator.pos {
|
|
||||||
idx := iterator.pos
|
|
||||||
val := iterator.values.Get(values.NewInt(idx))
|
|
||||||
iterator.pos++
|
|
||||||
|
|
||||||
return val, values.NewInt(idx), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return values.None, values.None, ErrExhausted
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewObjectIterator(input *values.Object) *ObjectIterator {
|
|
||||||
return &ObjectIterator{input, nil, 0}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (iterator *ObjectIterator) HasNext() bool {
|
|
||||||
// lazy initialization
|
|
||||||
if iterator.keys == nil {
|
|
||||||
iterator.keys = iterator.values.Keys()
|
|
||||||
}
|
|
||||||
|
|
||||||
return len(iterator.keys) > iterator.pos
|
|
||||||
}
|
|
||||||
|
|
||||||
func (iterator *ObjectIterator) Next() (core.Value, core.Value, error) {
|
|
||||||
if len(iterator.keys) > iterator.pos {
|
|
||||||
key := iterator.keys[iterator.pos]
|
|
||||||
val, _ := iterator.values.Get(values.NewString(key))
|
|
||||||
iterator.pos++
|
|
||||||
|
|
||||||
return val, values.NewString(key), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return values.None, values.None, ErrExhausted
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewHTMLNodeIterator(input values.HTMLNode) *HTMLNodeIterator {
|
|
||||||
return &HTMLNodeIterator{input, 0}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (iterator *HTMLNodeIterator) HasNext() bool {
|
|
||||||
return iterator.values.Length() > values.NewInt(iterator.pos)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (iterator *HTMLNodeIterator) Next() (core.Value, core.Value, error) {
|
|
||||||
if iterator.values.Length() > values.NewInt(iterator.pos) {
|
|
||||||
idx := iterator.pos
|
|
||||||
val := iterator.values.GetChildNode(values.NewInt(idx))
|
|
||||||
|
|
||||||
iterator.pos++
|
|
||||||
|
|
||||||
return val, values.NewInt(idx), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return values.None, values.None, ErrExhausted
|
|
||||||
}
|
|
||||||
|
@ -1,356 +0,0 @@
|
|||||||
package collections_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/MontFerret/ferret/pkg/runtime/collections"
|
|
||||||
"github.com/MontFerret/ferret/pkg/runtime/core"
|
|
||||||
"github.com/MontFerret/ferret/pkg/runtime/values"
|
|
||||||
. "github.com/smartystreets/goconvey/convey"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestSliceIterator(t *testing.T) {
|
|
||||||
Convey("Should iterate over a slice", t, func() {
|
|
||||||
arr := []core.Value{
|
|
||||||
values.NewInt(1),
|
|
||||||
values.NewInt(2),
|
|
||||||
values.NewInt(3),
|
|
||||||
values.NewInt(4),
|
|
||||||
values.NewInt(5),
|
|
||||||
}
|
|
||||||
|
|
||||||
iter := collections.NewSliceIterator(arr)
|
|
||||||
|
|
||||||
res := make([]core.Value, 0, len(arr))
|
|
||||||
|
|
||||||
pos := 0
|
|
||||||
|
|
||||||
for iter.HasNext() {
|
|
||||||
item, key, err := iter.Next()
|
|
||||||
|
|
||||||
So(err, ShouldBeNil)
|
|
||||||
So(key.Unwrap(), ShouldEqual, pos)
|
|
||||||
|
|
||||||
res = append(res, item)
|
|
||||||
|
|
||||||
pos += 1
|
|
||||||
}
|
|
||||||
|
|
||||||
So(res, ShouldHaveLength, len(arr))
|
|
||||||
})
|
|
||||||
|
|
||||||
Convey("Should iterate over a slice in the same order", t, func() {
|
|
||||||
arr := []core.Value{
|
|
||||||
values.NewInt(1),
|
|
||||||
values.NewInt(2),
|
|
||||||
values.NewInt(3),
|
|
||||||
values.NewInt(4),
|
|
||||||
values.NewInt(5),
|
|
||||||
}
|
|
||||||
|
|
||||||
iter := collections.NewSliceIterator(arr)
|
|
||||||
|
|
||||||
res := make([]core.Value, 0, len(arr))
|
|
||||||
|
|
||||||
for iter.HasNext() {
|
|
||||||
item, _, err := iter.Next()
|
|
||||||
|
|
||||||
So(err, ShouldBeNil)
|
|
||||||
|
|
||||||
res = append(res, item)
|
|
||||||
}
|
|
||||||
|
|
||||||
for idx := range arr {
|
|
||||||
expected := arr[idx]
|
|
||||||
actual := res[idx]
|
|
||||||
|
|
||||||
So(actual, ShouldEqual, expected)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
Convey("Should return an error when exhausted", t, func() {
|
|
||||||
arr := []core.Value{
|
|
||||||
values.NewInt(1),
|
|
||||||
values.NewInt(2),
|
|
||||||
values.NewInt(3),
|
|
||||||
values.NewInt(4),
|
|
||||||
values.NewInt(5),
|
|
||||||
}
|
|
||||||
|
|
||||||
iter := collections.NewSliceIterator(arr)
|
|
||||||
|
|
||||||
res := make([]core.Value, 0, len(arr))
|
|
||||||
|
|
||||||
for iter.HasNext() {
|
|
||||||
item, _, err := iter.Next()
|
|
||||||
|
|
||||||
So(err, ShouldBeNil)
|
|
||||||
|
|
||||||
res = append(res, item)
|
|
||||||
}
|
|
||||||
|
|
||||||
item, _, err := iter.Next()
|
|
||||||
|
|
||||||
So(item, ShouldEqual, values.None)
|
|
||||||
So(err, ShouldBeError)
|
|
||||||
})
|
|
||||||
|
|
||||||
Convey("Should NOT iterate over an empty slice", t, func() {
|
|
||||||
arr := []core.Value{}
|
|
||||||
|
|
||||||
iter := collections.NewSliceIterator(arr)
|
|
||||||
|
|
||||||
var iterated bool
|
|
||||||
|
|
||||||
for iter.HasNext() {
|
|
||||||
iterated = true
|
|
||||||
}
|
|
||||||
|
|
||||||
So(iterated, ShouldBeFalse)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestMapIterator(t *testing.T) {
|
|
||||||
Convey("Should iterate over a map", t, func() {
|
|
||||||
m := map[string]core.Value{
|
|
||||||
"one": values.NewInt(1),
|
|
||||||
"two": values.NewInt(2),
|
|
||||||
"three": values.NewInt(3),
|
|
||||||
"four": values.NewInt(4),
|
|
||||||
"five": values.NewInt(5),
|
|
||||||
}
|
|
||||||
|
|
||||||
iter := collections.NewMapIterator(m)
|
|
||||||
|
|
||||||
res := make([]core.Value, 0, len(m))
|
|
||||||
|
|
||||||
for iter.HasNext() {
|
|
||||||
item, key, err := iter.Next()
|
|
||||||
|
|
||||||
So(err, ShouldBeNil)
|
|
||||||
|
|
||||||
expected, exists := m[key.String()]
|
|
||||||
|
|
||||||
So(exists, ShouldBeTrue)
|
|
||||||
So(expected, ShouldEqual, item)
|
|
||||||
|
|
||||||
res = append(res, item)
|
|
||||||
}
|
|
||||||
|
|
||||||
So(res, ShouldHaveLength, len(m))
|
|
||||||
})
|
|
||||||
|
|
||||||
Convey("Should return an error when exhausted", t, func() {
|
|
||||||
m := map[string]core.Value{
|
|
||||||
"one": values.NewInt(1),
|
|
||||||
"two": values.NewInt(2),
|
|
||||||
"three": values.NewInt(3),
|
|
||||||
"four": values.NewInt(4),
|
|
||||||
"five": values.NewInt(5),
|
|
||||||
}
|
|
||||||
|
|
||||||
iter := collections.NewMapIterator(m)
|
|
||||||
|
|
||||||
res := make([]core.Value, 0, len(m))
|
|
||||||
|
|
||||||
for iter.HasNext() {
|
|
||||||
item, _, err := iter.Next()
|
|
||||||
|
|
||||||
So(err, ShouldBeNil)
|
|
||||||
|
|
||||||
res = append(res, item)
|
|
||||||
}
|
|
||||||
|
|
||||||
item, _, err := iter.Next()
|
|
||||||
|
|
||||||
So(item, ShouldEqual, values.None)
|
|
||||||
So(err, ShouldBeError)
|
|
||||||
})
|
|
||||||
|
|
||||||
Convey("Should NOT iterate over a empty map", t, func() {
|
|
||||||
m := make(map[string]core.Value)
|
|
||||||
|
|
||||||
iter := collections.NewMapIterator(m)
|
|
||||||
|
|
||||||
var iterated bool
|
|
||||||
|
|
||||||
for iter.HasNext() {
|
|
||||||
iterated = true
|
|
||||||
}
|
|
||||||
|
|
||||||
So(iterated, ShouldBeFalse)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestArrayIterator(t *testing.T) {
|
|
||||||
Convey("Should iterate over an array", t, func() {
|
|
||||||
arr := values.NewArrayWith(
|
|
||||||
values.NewInt(1),
|
|
||||||
values.NewInt(2),
|
|
||||||
values.NewInt(3),
|
|
||||||
values.NewInt(4),
|
|
||||||
values.NewInt(5),
|
|
||||||
)
|
|
||||||
|
|
||||||
iter := collections.NewArrayIterator(arr)
|
|
||||||
|
|
||||||
res := make([]core.Value, 0, arr.Length())
|
|
||||||
|
|
||||||
pos := 0
|
|
||||||
|
|
||||||
for iter.HasNext() {
|
|
||||||
item, key, err := iter.Next()
|
|
||||||
|
|
||||||
So(err, ShouldBeNil)
|
|
||||||
So(key.Unwrap(), ShouldEqual, pos)
|
|
||||||
|
|
||||||
res = append(res, item)
|
|
||||||
|
|
||||||
pos += 1
|
|
||||||
}
|
|
||||||
|
|
||||||
So(res, ShouldHaveLength, arr.Length())
|
|
||||||
})
|
|
||||||
|
|
||||||
Convey("Should iterate over an array in the same order", t, func() {
|
|
||||||
arr := values.NewArrayWith(
|
|
||||||
values.NewInt(1),
|
|
||||||
values.NewInt(2),
|
|
||||||
values.NewInt(3),
|
|
||||||
values.NewInt(4),
|
|
||||||
values.NewInt(5),
|
|
||||||
)
|
|
||||||
|
|
||||||
iter := collections.NewArrayIterator(arr)
|
|
||||||
|
|
||||||
res := make([]core.Value, 0, arr.Length())
|
|
||||||
|
|
||||||
for iter.HasNext() {
|
|
||||||
item, _, err := iter.Next()
|
|
||||||
|
|
||||||
So(err, ShouldBeNil)
|
|
||||||
|
|
||||||
res = append(res, item)
|
|
||||||
}
|
|
||||||
|
|
||||||
arr.ForEach(func(expected core.Value, idx int) bool {
|
|
||||||
actual := res[idx]
|
|
||||||
|
|
||||||
So(actual, ShouldEqual, expected)
|
|
||||||
|
|
||||||
return true
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
Convey("Should return an error when exhausted", t, func() {
|
|
||||||
arr := values.NewArrayWith(
|
|
||||||
values.NewInt(1),
|
|
||||||
values.NewInt(2),
|
|
||||||
values.NewInt(3),
|
|
||||||
values.NewInt(4),
|
|
||||||
values.NewInt(5),
|
|
||||||
)
|
|
||||||
|
|
||||||
iter := collections.NewArrayIterator(arr)
|
|
||||||
|
|
||||||
res := make([]core.Value, 0, arr.Length())
|
|
||||||
|
|
||||||
for iter.HasNext() {
|
|
||||||
item, _, err := iter.Next()
|
|
||||||
|
|
||||||
So(err, ShouldBeNil)
|
|
||||||
|
|
||||||
res = append(res, item)
|
|
||||||
}
|
|
||||||
|
|
||||||
item, _, err := iter.Next()
|
|
||||||
|
|
||||||
So(item, ShouldEqual, values.None)
|
|
||||||
So(err, ShouldBeError)
|
|
||||||
})
|
|
||||||
|
|
||||||
Convey("Should NOT iterate over an empty array", t, func() {
|
|
||||||
arr := values.NewArray(10)
|
|
||||||
|
|
||||||
iter := collections.NewArrayIterator(arr)
|
|
||||||
|
|
||||||
var iterated bool
|
|
||||||
|
|
||||||
for iter.HasNext() {
|
|
||||||
iterated = true
|
|
||||||
}
|
|
||||||
|
|
||||||
So(iterated, ShouldBeFalse)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestObjectIterator(t *testing.T) {
|
|
||||||
Convey("Should iterate over a map", t, func() {
|
|
||||||
m := values.NewObjectWith(
|
|
||||||
values.NewObjectProperty("one", values.NewInt(1)),
|
|
||||||
values.NewObjectProperty("two", values.NewInt(2)),
|
|
||||||
values.NewObjectProperty("three", values.NewInt(3)),
|
|
||||||
values.NewObjectProperty("four", values.NewInt(4)),
|
|
||||||
values.NewObjectProperty("five", values.NewInt(5)),
|
|
||||||
)
|
|
||||||
|
|
||||||
iter := collections.NewObjectIterator(m)
|
|
||||||
|
|
||||||
res := make([]core.Value, 0, m.Length())
|
|
||||||
|
|
||||||
for iter.HasNext() {
|
|
||||||
item, key, err := iter.Next()
|
|
||||||
|
|
||||||
So(err, ShouldBeNil)
|
|
||||||
|
|
||||||
expected, exists := m.Get(values.NewString(key.String()))
|
|
||||||
|
|
||||||
So(bool(exists), ShouldBeTrue)
|
|
||||||
So(expected, ShouldEqual, item)
|
|
||||||
|
|
||||||
res = append(res, item)
|
|
||||||
}
|
|
||||||
|
|
||||||
So(res, ShouldHaveLength, m.Length())
|
|
||||||
})
|
|
||||||
|
|
||||||
Convey("Should return an error when exhausted", t, func() {
|
|
||||||
m := values.NewObjectWith(
|
|
||||||
values.NewObjectProperty("one", values.NewInt(1)),
|
|
||||||
values.NewObjectProperty("two", values.NewInt(2)),
|
|
||||||
values.NewObjectProperty("three", values.NewInt(3)),
|
|
||||||
values.NewObjectProperty("four", values.NewInt(4)),
|
|
||||||
values.NewObjectProperty("five", values.NewInt(5)),
|
|
||||||
)
|
|
||||||
|
|
||||||
iter := collections.NewObjectIterator(m)
|
|
||||||
|
|
||||||
res := make([]core.Value, 0, m.Length())
|
|
||||||
|
|
||||||
for iter.HasNext() {
|
|
||||||
item, _, err := iter.Next()
|
|
||||||
|
|
||||||
So(err, ShouldBeNil)
|
|
||||||
|
|
||||||
res = append(res, item)
|
|
||||||
}
|
|
||||||
|
|
||||||
item, _, err := iter.Next()
|
|
||||||
|
|
||||||
So(item, ShouldEqual, values.None)
|
|
||||||
So(err, ShouldBeError)
|
|
||||||
})
|
|
||||||
|
|
||||||
Convey("Should NOT iterate over a empty map", t, func() {
|
|
||||||
m := values.NewObject()
|
|
||||||
|
|
||||||
iter := collections.NewObjectIterator(m)
|
|
||||||
|
|
||||||
var iterated bool
|
|
||||||
|
|
||||||
for iter.HasNext() {
|
|
||||||
iterated = true
|
|
||||||
}
|
|
||||||
|
|
||||||
So(iterated, ShouldBeFalse)
|
|
||||||
})
|
|
||||||
}
|
|
49
pkg/runtime/collections/keyed.go
Normal file
49
pkg/runtime/collections/keyed.go
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
package collections
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/MontFerret/ferret/pkg/runtime/values"
|
||||||
|
)
|
||||||
|
|
||||||
|
type KeyedIterator struct {
|
||||||
|
valVar string
|
||||||
|
keyVar string
|
||||||
|
values KeyedCollection
|
||||||
|
keys []string
|
||||||
|
pos int
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewKeyedIterator(
|
||||||
|
valVar,
|
||||||
|
keyVar string,
|
||||||
|
input KeyedCollection,
|
||||||
|
) Iterator {
|
||||||
|
return &KeyedIterator{valVar, keyVar, input, nil, 0}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewDefaultKeyedIterator(input KeyedCollection) Iterator {
|
||||||
|
return NewKeyedIterator(DefaultValueVar, DefaultKeyVar, input)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (iterator *KeyedIterator) HasNext() bool {
|
||||||
|
// lazy initialization
|
||||||
|
if iterator.keys == nil {
|
||||||
|
iterator.keys = iterator.values.Keys()
|
||||||
|
}
|
||||||
|
|
||||||
|
return len(iterator.keys) > iterator.pos
|
||||||
|
}
|
||||||
|
|
||||||
|
func (iterator *KeyedIterator) Next() (DataSet, error) {
|
||||||
|
if len(iterator.keys) > iterator.pos {
|
||||||
|
key := values.NewString(iterator.keys[iterator.pos])
|
||||||
|
val, _ := iterator.values.Get(key)
|
||||||
|
iterator.pos++
|
||||||
|
|
||||||
|
return DataSet{
|
||||||
|
iterator.valVar: val,
|
||||||
|
iterator.keyVar: key,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, ErrExhausted
|
||||||
|
}
|
85
pkg/runtime/collections/keyed_test.go
Normal file
85
pkg/runtime/collections/keyed_test.go
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
package collections_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/MontFerret/ferret/pkg/runtime/collections"
|
||||||
|
"github.com/MontFerret/ferret/pkg/runtime/core"
|
||||||
|
"github.com/MontFerret/ferret/pkg/runtime/values"
|
||||||
|
. "github.com/smartystreets/goconvey/convey"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func objectIterator(obj *values.Object) collections.Iterator {
|
||||||
|
return collections.NewDefaultKeyedIterator(obj)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestObjectIterator(t *testing.T) {
|
||||||
|
Convey("Should iterate over a map", t, func() {
|
||||||
|
m := values.NewObjectWith(
|
||||||
|
values.NewObjectProperty("one", values.NewInt(1)),
|
||||||
|
values.NewObjectProperty("two", values.NewInt(2)),
|
||||||
|
values.NewObjectProperty("three", values.NewInt(3)),
|
||||||
|
values.NewObjectProperty("four", values.NewInt(4)),
|
||||||
|
values.NewObjectProperty("five", values.NewInt(5)),
|
||||||
|
)
|
||||||
|
|
||||||
|
iter := objectIterator(m)
|
||||||
|
|
||||||
|
res := make([]core.Value, 0, m.Length())
|
||||||
|
|
||||||
|
for iter.HasNext() {
|
||||||
|
item, key, err := next(iter)
|
||||||
|
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
expected, exists := m.Get(values.NewString(key.String()))
|
||||||
|
|
||||||
|
So(bool(exists), ShouldBeTrue)
|
||||||
|
So(expected, ShouldEqual, item)
|
||||||
|
|
||||||
|
res = append(res, item)
|
||||||
|
}
|
||||||
|
|
||||||
|
So(res, ShouldHaveLength, m.Length())
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("Should return an error when exhausted", t, func() {
|
||||||
|
m := values.NewObjectWith(
|
||||||
|
values.NewObjectProperty("one", values.NewInt(1)),
|
||||||
|
values.NewObjectProperty("two", values.NewInt(2)),
|
||||||
|
values.NewObjectProperty("three", values.NewInt(3)),
|
||||||
|
values.NewObjectProperty("four", values.NewInt(4)),
|
||||||
|
values.NewObjectProperty("five", values.NewInt(5)),
|
||||||
|
)
|
||||||
|
|
||||||
|
iter := objectIterator(m)
|
||||||
|
|
||||||
|
res := make([]core.Value, 0, m.Length())
|
||||||
|
|
||||||
|
for iter.HasNext() {
|
||||||
|
item, _, err := next(iter)
|
||||||
|
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
res = append(res, item)
|
||||||
|
}
|
||||||
|
|
||||||
|
item, _, err := next(iter)
|
||||||
|
|
||||||
|
So(item, ShouldBeNil)
|
||||||
|
So(err, ShouldBeError)
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("Should NOT iterate over a empty map", t, func() {
|
||||||
|
m := values.NewObject()
|
||||||
|
|
||||||
|
iter := objectIterator(m)
|
||||||
|
|
||||||
|
var iterated bool
|
||||||
|
|
||||||
|
for iter.HasNext() {
|
||||||
|
iterated = true
|
||||||
|
}
|
||||||
|
|
||||||
|
So(iterated, ShouldBeFalse)
|
||||||
|
})
|
||||||
|
}
|
@ -30,14 +30,14 @@ func (i *LimitIterator) HasNext() bool {
|
|||||||
return i.counter() < i.count
|
return i.counter() < i.count
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *LimitIterator) Next() (core.Value, core.Value, error) {
|
func (i *LimitIterator) Next() (DataSet, error) {
|
||||||
if i.counter() <= i.count {
|
if i.counter() <= i.count {
|
||||||
i.currCount++
|
i.currCount++
|
||||||
|
|
||||||
return i.src.Next()
|
return i.src.Next()
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil, nil, ErrExhausted
|
return nil, ErrExhausted
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *LimitIterator) counter() int {
|
func (i *LimitIterator) counter() int {
|
||||||
|
@ -19,7 +19,7 @@ func TestLimit(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
src, err := collections.NewLimitIterator(
|
src, err := collections.NewLimitIterator(
|
||||||
collections.NewSliceIterator(arr),
|
sliceIterator(arr),
|
||||||
1,
|
1,
|
||||||
0,
|
0,
|
||||||
)
|
)
|
||||||
@ -29,7 +29,7 @@ func TestLimit(t *testing.T) {
|
|||||||
res := make([]core.Value, 0, len(arr))
|
res := make([]core.Value, 0, len(arr))
|
||||||
|
|
||||||
for src.HasNext() {
|
for src.HasNext() {
|
||||||
item, _, err := src.Next()
|
item, _, err := next(src)
|
||||||
|
|
||||||
So(err, ShouldBeNil)
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
@ -49,7 +49,7 @@ func TestLimit(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
src, err := collections.NewLimitIterator(
|
src, err := collections.NewLimitIterator(
|
||||||
collections.NewSliceIterator(arr),
|
sliceIterator(arr),
|
||||||
2,
|
2,
|
||||||
0,
|
0,
|
||||||
)
|
)
|
||||||
@ -59,7 +59,7 @@ func TestLimit(t *testing.T) {
|
|||||||
res := make([]core.Value, 0, len(arr))
|
res := make([]core.Value, 0, len(arr))
|
||||||
|
|
||||||
for src.HasNext() {
|
for src.HasNext() {
|
||||||
item, _, err := src.Next()
|
item, _, err := next(src)
|
||||||
|
|
||||||
So(err, ShouldBeNil)
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
@ -80,7 +80,7 @@ func TestLimit(t *testing.T) {
|
|||||||
|
|
||||||
offset := 2
|
offset := 2
|
||||||
src, err := collections.NewLimitIterator(
|
src, err := collections.NewLimitIterator(
|
||||||
collections.NewSliceIterator(arr),
|
sliceIterator(arr),
|
||||||
2,
|
2,
|
||||||
offset,
|
offset,
|
||||||
)
|
)
|
||||||
@ -90,7 +90,7 @@ func TestLimit(t *testing.T) {
|
|||||||
res := make([]core.Value, 0, len(arr))
|
res := make([]core.Value, 0, len(arr))
|
||||||
|
|
||||||
for src.HasNext() {
|
for src.HasNext() {
|
||||||
item, _, err := src.Next()
|
item, _, err := next(src)
|
||||||
|
|
||||||
So(err, ShouldBeNil)
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
@ -118,7 +118,7 @@ func TestLimit(t *testing.T) {
|
|||||||
offset := 3
|
offset := 3
|
||||||
|
|
||||||
src, err := collections.NewLimitIterator(
|
src, err := collections.NewLimitIterator(
|
||||||
collections.NewSliceIterator(arr),
|
sliceIterator(arr),
|
||||||
2,
|
2,
|
||||||
offset,
|
offset,
|
||||||
)
|
)
|
||||||
@ -128,7 +128,7 @@ func TestLimit(t *testing.T) {
|
|||||||
res := make([]core.Value, 0, len(arr))
|
res := make([]core.Value, 0, len(arr))
|
||||||
|
|
||||||
for src.HasNext() {
|
for src.HasNext() {
|
||||||
item, _, err := src.Next()
|
item, _, err := next(src)
|
||||||
|
|
||||||
So(err, ShouldBeNil)
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
@ -156,7 +156,7 @@ func TestLimit(t *testing.T) {
|
|||||||
offset := 4
|
offset := 4
|
||||||
|
|
||||||
src, err := collections.NewLimitIterator(
|
src, err := collections.NewLimitIterator(
|
||||||
collections.NewSliceIterator(arr),
|
sliceIterator(arr),
|
||||||
2,
|
2,
|
||||||
offset,
|
offset,
|
||||||
)
|
)
|
||||||
@ -166,7 +166,7 @@ func TestLimit(t *testing.T) {
|
|||||||
res := make([]core.Value, 0, len(arr))
|
res := make([]core.Value, 0, len(arr))
|
||||||
|
|
||||||
for src.HasNext() {
|
for src.HasNext() {
|
||||||
item, _, err := src.Next()
|
item, _, err := next(src)
|
||||||
|
|
||||||
So(err, ShouldBeNil)
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
60
pkg/runtime/collections/map.go
Normal file
60
pkg/runtime/collections/map.go
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
package collections
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/MontFerret/ferret/pkg/runtime/core"
|
||||||
|
"github.com/MontFerret/ferret/pkg/runtime/values"
|
||||||
|
)
|
||||||
|
|
||||||
|
type MapIterator struct {
|
||||||
|
valVar string
|
||||||
|
keyVar string
|
||||||
|
values map[string]core.Value
|
||||||
|
keys []string
|
||||||
|
pos int
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewMapIterator(
|
||||||
|
valVar,
|
||||||
|
keyVar string,
|
||||||
|
input map[string]core.Value,
|
||||||
|
) Iterator {
|
||||||
|
return &MapIterator{valVar, keyVar, input, nil, 0}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewDefaultMapIterator(
|
||||||
|
input map[string]core.Value,
|
||||||
|
) Iterator {
|
||||||
|
return &MapIterator{DefaultValueVar, DefaultKeyVar, input, nil, 0}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (iterator *MapIterator) HasNext() bool {
|
||||||
|
// lazy initialization
|
||||||
|
if iterator.keys == nil {
|
||||||
|
keys := make([]string, len(iterator.values))
|
||||||
|
|
||||||
|
i := 0
|
||||||
|
for k := range iterator.values {
|
||||||
|
keys[i] = k
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
|
||||||
|
iterator.keys = keys
|
||||||
|
}
|
||||||
|
|
||||||
|
return len(iterator.keys) > iterator.pos
|
||||||
|
}
|
||||||
|
|
||||||
|
func (iterator *MapIterator) Next() (DataSet, error) {
|
||||||
|
if len(iterator.keys) > iterator.pos {
|
||||||
|
key := iterator.keys[iterator.pos]
|
||||||
|
val := iterator.values[key]
|
||||||
|
iterator.pos++
|
||||||
|
|
||||||
|
return DataSet{
|
||||||
|
iterator.valVar: val,
|
||||||
|
iterator.keyVar: values.NewString(key),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, ErrExhausted
|
||||||
|
}
|
85
pkg/runtime/collections/map_test.go
Normal file
85
pkg/runtime/collections/map_test.go
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
package collections_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/MontFerret/ferret/pkg/runtime/collections"
|
||||||
|
"github.com/MontFerret/ferret/pkg/runtime/core"
|
||||||
|
"github.com/MontFerret/ferret/pkg/runtime/values"
|
||||||
|
. "github.com/smartystreets/goconvey/convey"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func mapIterator(m map[string]core.Value) collections.Iterator {
|
||||||
|
return collections.NewDefaultMapIterator(m)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMapIterator(t *testing.T) {
|
||||||
|
Convey("Should iterate over a map", t, func() {
|
||||||
|
m := map[string]core.Value{
|
||||||
|
"one": values.NewInt(1),
|
||||||
|
"two": values.NewInt(2),
|
||||||
|
"three": values.NewInt(3),
|
||||||
|
"four": values.NewInt(4),
|
||||||
|
"five": values.NewInt(5),
|
||||||
|
}
|
||||||
|
|
||||||
|
iter := mapIterator(m)
|
||||||
|
|
||||||
|
res := make([]core.Value, 0, len(m))
|
||||||
|
|
||||||
|
for iter.HasNext() {
|
||||||
|
item, key, err := next(iter)
|
||||||
|
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
expected, exists := m[key.String()]
|
||||||
|
|
||||||
|
So(exists, ShouldBeTrue)
|
||||||
|
So(expected, ShouldEqual, item)
|
||||||
|
|
||||||
|
res = append(res, item)
|
||||||
|
}
|
||||||
|
|
||||||
|
So(res, ShouldHaveLength, len(m))
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("Should return an error when exhausted", t, func() {
|
||||||
|
m := map[string]core.Value{
|
||||||
|
"one": values.NewInt(1),
|
||||||
|
"two": values.NewInt(2),
|
||||||
|
"three": values.NewInt(3),
|
||||||
|
"four": values.NewInt(4),
|
||||||
|
"five": values.NewInt(5),
|
||||||
|
}
|
||||||
|
|
||||||
|
iter := mapIterator(m)
|
||||||
|
|
||||||
|
res := make([]core.Value, 0, len(m))
|
||||||
|
|
||||||
|
for iter.HasNext() {
|
||||||
|
item, _, err := next(iter)
|
||||||
|
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
res = append(res, item)
|
||||||
|
}
|
||||||
|
|
||||||
|
item, _, err := next(iter)
|
||||||
|
|
||||||
|
So(item, ShouldBeNil)
|
||||||
|
So(err, ShouldBeError)
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("Should NOT iterate over a empty map", t, func() {
|
||||||
|
m := make(map[string]core.Value)
|
||||||
|
|
||||||
|
iter := mapIterator(m)
|
||||||
|
|
||||||
|
var iterated bool
|
||||||
|
|
||||||
|
for iter.HasNext() {
|
||||||
|
iterated = true
|
||||||
|
}
|
||||||
|
|
||||||
|
So(iterated, ShouldBeFalse)
|
||||||
|
})
|
||||||
|
}
|
13
pkg/runtime/collections/noop.go
Normal file
13
pkg/runtime/collections/noop.go
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
package collections
|
||||||
|
|
||||||
|
type noopIterator struct{}
|
||||||
|
|
||||||
|
var NoopIterator = &noopIterator{}
|
||||||
|
|
||||||
|
func (iterator *noopIterator) HasNext() bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (iterator *noopIterator) Next() (DataSet, error) {
|
||||||
|
return nil, ErrExhausted
|
||||||
|
}
|
@ -1,21 +0,0 @@
|
|||||||
package collections
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"github.com/MontFerret/ferret/pkg/runtime/core"
|
|
||||||
)
|
|
||||||
|
|
||||||
type (
|
|
||||||
Reducer interface {
|
|
||||||
Reduce(collection core.Value, value core.Value) (core.Value, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
Reducible interface {
|
|
||||||
Reduce() Reducer
|
|
||||||
}
|
|
||||||
|
|
||||||
ReducibleExpression interface {
|
|
||||||
core.Expression
|
|
||||||
Reduce(ctx context.Context, scope *core.Scope) (Reducer, error)
|
|
||||||
}
|
|
||||||
)
|
|
44
pkg/runtime/collections/slice.go
Normal file
44
pkg/runtime/collections/slice.go
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
package collections
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/MontFerret/ferret/pkg/runtime/core"
|
||||||
|
"github.com/MontFerret/ferret/pkg/runtime/values"
|
||||||
|
)
|
||||||
|
|
||||||
|
type SliceIterator struct {
|
||||||
|
valVar string
|
||||||
|
keyVar string
|
||||||
|
values []core.Value
|
||||||
|
pos int
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewSliceIterator(
|
||||||
|
valVar,
|
||||||
|
keyVar string,
|
||||||
|
input []core.Value,
|
||||||
|
) Iterator {
|
||||||
|
return &SliceIterator{valVar, keyVar, input, 0}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewDefaultSliceIterator(input []core.Value) Iterator {
|
||||||
|
return NewSliceIterator(DefaultValueVar, DefaultKeyVar, input)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (iterator *SliceIterator) HasNext() bool {
|
||||||
|
return len(iterator.values) > iterator.pos
|
||||||
|
}
|
||||||
|
|
||||||
|
func (iterator *SliceIterator) Next() (DataSet, error) {
|
||||||
|
if len(iterator.values) > iterator.pos {
|
||||||
|
idx := iterator.pos
|
||||||
|
val := iterator.values[idx]
|
||||||
|
iterator.pos++
|
||||||
|
|
||||||
|
return DataSet{
|
||||||
|
iterator.valVar: val,
|
||||||
|
iterator.keyVar: values.NewInt(idx),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, ErrExhausted
|
||||||
|
}
|
114
pkg/runtime/collections/slice_test.go
Normal file
114
pkg/runtime/collections/slice_test.go
Normal file
@ -0,0 +1,114 @@
|
|||||||
|
package collections_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/MontFerret/ferret/pkg/runtime/collections"
|
||||||
|
"github.com/MontFerret/ferret/pkg/runtime/core"
|
||||||
|
"github.com/MontFerret/ferret/pkg/runtime/values"
|
||||||
|
. "github.com/smartystreets/goconvey/convey"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func sliceIterator(value []core.Value) collections.Iterator {
|
||||||
|
return collections.NewDefaultSliceIterator(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSliceIterator(t *testing.T) {
|
||||||
|
Convey("Should iterate over a slice", t, func() {
|
||||||
|
arr := []core.Value{
|
||||||
|
values.NewInt(1),
|
||||||
|
values.NewInt(2),
|
||||||
|
values.NewInt(3),
|
||||||
|
values.NewInt(4),
|
||||||
|
values.NewInt(5),
|
||||||
|
}
|
||||||
|
|
||||||
|
iter := sliceIterator(arr)
|
||||||
|
|
||||||
|
res := make([]core.Value, 0, len(arr))
|
||||||
|
|
||||||
|
pos := 0
|
||||||
|
|
||||||
|
for iter.HasNext() {
|
||||||
|
item, key, err := next(iter)
|
||||||
|
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
So(key.Unwrap(), ShouldEqual, pos)
|
||||||
|
|
||||||
|
res = append(res, item)
|
||||||
|
|
||||||
|
pos += 1
|
||||||
|
}
|
||||||
|
|
||||||
|
So(res, ShouldHaveLength, len(arr))
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("Should iterate over a slice in the same order", t, func() {
|
||||||
|
arr := []core.Value{
|
||||||
|
values.NewInt(1),
|
||||||
|
values.NewInt(2),
|
||||||
|
values.NewInt(3),
|
||||||
|
values.NewInt(4),
|
||||||
|
values.NewInt(5),
|
||||||
|
}
|
||||||
|
|
||||||
|
iter := sliceIterator(arr)
|
||||||
|
|
||||||
|
res := make([]core.Value, 0, len(arr))
|
||||||
|
|
||||||
|
for iter.HasNext() {
|
||||||
|
item, _, err := next(iter)
|
||||||
|
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
res = append(res, item)
|
||||||
|
}
|
||||||
|
|
||||||
|
for idx := range arr {
|
||||||
|
expected := arr[idx]
|
||||||
|
actual := res[idx]
|
||||||
|
|
||||||
|
So(actual, ShouldEqual, expected)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("Should return an error when exhausted", t, func() {
|
||||||
|
arr := []core.Value{
|
||||||
|
values.NewInt(1),
|
||||||
|
values.NewInt(2),
|
||||||
|
values.NewInt(3),
|
||||||
|
values.NewInt(4),
|
||||||
|
values.NewInt(5),
|
||||||
|
}
|
||||||
|
|
||||||
|
iter := sliceIterator(arr)
|
||||||
|
|
||||||
|
res := make([]core.Value, 0, len(arr))
|
||||||
|
|
||||||
|
for iter.HasNext() {
|
||||||
|
item, _, err := next(iter)
|
||||||
|
|
||||||
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
res = append(res, item)
|
||||||
|
}
|
||||||
|
|
||||||
|
item, _, err := next(iter)
|
||||||
|
|
||||||
|
So(item, ShouldBeNil)
|
||||||
|
So(err, ShouldBeError)
|
||||||
|
})
|
||||||
|
|
||||||
|
Convey("Should NOT iterate over an empty slice", t, func() {
|
||||||
|
arr := []core.Value{}
|
||||||
|
|
||||||
|
iter := sliceIterator(arr)
|
||||||
|
|
||||||
|
var iterated bool
|
||||||
|
|
||||||
|
for iter.HasNext() {
|
||||||
|
iterated = true
|
||||||
|
}
|
||||||
|
|
||||||
|
So(iterated, ShouldBeFalse)
|
||||||
|
})
|
||||||
|
}
|
@ -10,7 +10,7 @@ import (
|
|||||||
type (
|
type (
|
||||||
SortDirection int
|
SortDirection int
|
||||||
|
|
||||||
Comparator func(first core.Value, second core.Value) (int, error)
|
Comparator func(first DataSet, second DataSet) (int, error)
|
||||||
|
|
||||||
Sorter struct {
|
Sorter struct {
|
||||||
fn Comparator
|
fn Comparator
|
||||||
@ -21,7 +21,9 @@ type (
|
|||||||
src Iterator
|
src Iterator
|
||||||
sorters []*Sorter
|
sorters []*Sorter
|
||||||
ready bool
|
ready bool
|
||||||
values *SliceIterator
|
values []DataSet
|
||||||
|
err error
|
||||||
|
pos int
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -71,33 +73,53 @@ func NewSortIterator(
|
|||||||
return nil, errors.Wrap(core.ErrMissedArgument, "comparator")
|
return nil, errors.Wrap(core.ErrMissedArgument, "comparator")
|
||||||
}
|
}
|
||||||
|
|
||||||
return &SortIterator{src, comparators, false, nil}, nil
|
return &SortIterator{
|
||||||
|
src,
|
||||||
|
comparators,
|
||||||
|
false,
|
||||||
|
nil, nil,
|
||||||
|
0,
|
||||||
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (iterator *SortIterator) HasNext() bool {
|
func (iterator *SortIterator) HasNext() bool {
|
||||||
// we need to initialize the iterator
|
// we need to initialize the iterator
|
||||||
if iterator.ready == false {
|
if iterator.ready == false {
|
||||||
iterator.ready = true
|
iterator.ready = true
|
||||||
values, err := iterator.sort()
|
sorted, err := iterator.sort()
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// set to true because we do not want to initialize next time anymore
|
// dataSet to true because we do not want to initialize next time anymore
|
||||||
iterator.values = NewSliceIterator(make([]core.Value, 0, 0))
|
iterator.values = nil
|
||||||
|
iterator.err = err
|
||||||
|
|
||||||
return false
|
// if there is an error, we need to show it during Next()
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
iterator.values = values
|
iterator.values = sorted
|
||||||
}
|
}
|
||||||
|
|
||||||
return iterator.values.HasNext()
|
return iterator.values != nil && len(iterator.values) > iterator.pos
|
||||||
}
|
}
|
||||||
|
|
||||||
func (iterator *SortIterator) Next() (core.Value, core.Value, error) {
|
func (iterator *SortIterator) Next() (DataSet, error) {
|
||||||
return iterator.values.Next()
|
if iterator.err != nil {
|
||||||
|
return nil, iterator.err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(iterator.values) > iterator.pos {
|
||||||
|
idx := iterator.pos
|
||||||
|
val := iterator.values[idx]
|
||||||
|
iterator.pos++
|
||||||
|
|
||||||
|
return val, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, ErrExhausted
|
||||||
}
|
}
|
||||||
|
|
||||||
func (iterator *SortIterator) sort() (*SliceIterator, error) {
|
func (iterator *SortIterator) sort() ([]DataSet, error) {
|
||||||
res, err := ToSlice(iterator.src)
|
res, err := ToSlice(iterator.src)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -147,5 +169,5 @@ func (iterator *SortIterator) sort() (*SliceIterator, error) {
|
|||||||
return nil, failure
|
return nil, failure
|
||||||
}
|
}
|
||||||
|
|
||||||
return NewSliceIterator(res), nil
|
return res, nil
|
||||||
}
|
}
|
||||||
|
@ -9,6 +9,20 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func toValues(sets []collections.DataSet) []core.Value {
|
||||||
|
res := make([]core.Value, 0, len(sets))
|
||||||
|
|
||||||
|
for _, ds := range sets {
|
||||||
|
res = append(res, ds.Get(collections.DefaultValueVar))
|
||||||
|
}
|
||||||
|
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
func toArrayOfValues(sets []collections.DataSet) *values.Array {
|
||||||
|
return values.NewArrayWith(toValues(sets)...)
|
||||||
|
}
|
||||||
|
|
||||||
func TestSort(t *testing.T) {
|
func TestSort(t *testing.T) {
|
||||||
Convey("Should sort asc", t, func() {
|
Convey("Should sort asc", t, func() {
|
||||||
arr := []core.Value{
|
arr := []core.Value{
|
||||||
@ -20,14 +34,14 @@ func TestSort(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
s, _ := collections.NewSorter(
|
s, _ := collections.NewSorter(
|
||||||
func(first core.Value, second core.Value) (int, error) {
|
func(first collections.DataSet, second collections.DataSet) (int, error) {
|
||||||
return first.Compare(second), nil
|
return first.Get(collections.DefaultValueVar).Compare(second.Get(collections.DefaultValueVar)), nil
|
||||||
},
|
},
|
||||||
collections.SortDirectionAsc,
|
collections.SortDirectionAsc,
|
||||||
)
|
)
|
||||||
|
|
||||||
src, err := collections.NewSortIterator(
|
src, err := collections.NewSortIterator(
|
||||||
collections.NewSliceIterator(arr),
|
sliceIterator(arr),
|
||||||
s,
|
s,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -40,7 +54,7 @@ func TestSort(t *testing.T) {
|
|||||||
numbers := []int{1, 2, 3, 4, 5}
|
numbers := []int{1, 2, 3, 4, 5}
|
||||||
|
|
||||||
for idx, num := range numbers {
|
for idx, num := range numbers {
|
||||||
So(res[idx].Unwrap(), ShouldEqual, num)
|
So(res[idx].Get(collections.DefaultValueVar).Unwrap(), ShouldEqual, num)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -54,14 +68,14 @@ func TestSort(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
s, _ := collections.NewSorter(
|
s, _ := collections.NewSorter(
|
||||||
func(first core.Value, second core.Value) (int, error) {
|
func(first collections.DataSet, second collections.DataSet) (int, error) {
|
||||||
return first.Compare(second), nil
|
return first.Get(collections.DefaultValueVar).Compare(second.Get(collections.DefaultValueVar)), nil
|
||||||
},
|
},
|
||||||
collections.SortDirectionDesc,
|
collections.SortDirectionDesc,
|
||||||
)
|
)
|
||||||
|
|
||||||
src, err := collections.NewSortIterator(
|
src, err := collections.NewSortIterator(
|
||||||
collections.NewSliceIterator(arr),
|
sliceIterator(arr),
|
||||||
s,
|
s,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -74,7 +88,7 @@ func TestSort(t *testing.T) {
|
|||||||
numbers := []int{5, 4, 3, 2, 1}
|
numbers := []int{5, 4, 3, 2, 1}
|
||||||
|
|
||||||
for idx, num := range numbers {
|
for idx, num := range numbers {
|
||||||
So(res[idx].Unwrap(), ShouldEqual, num)
|
So(res[idx].Get(collections.DefaultValueVar).Unwrap(), ShouldEqual, num)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -100,9 +114,9 @@ func TestSort(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
s1, _ := collections.NewSorter(
|
s1, _ := collections.NewSorter(
|
||||||
func(first core.Value, second core.Value) (int, error) {
|
func(first collections.DataSet, second collections.DataSet) (int, error) {
|
||||||
o1, _ := first.(*values.Object).Get("one")
|
o1, _ := first.Get(collections.DefaultValueVar).(*values.Object).Get("one")
|
||||||
o2, _ := second.(*values.Object).Get("one")
|
o2, _ := second.Get(collections.DefaultValueVar).(*values.Object).Get("one")
|
||||||
|
|
||||||
return o1.Compare(o2), nil
|
return o1.Compare(o2), nil
|
||||||
},
|
},
|
||||||
@ -110,9 +124,9 @@ func TestSort(t *testing.T) {
|
|||||||
)
|
)
|
||||||
|
|
||||||
s2, _ := collections.NewSorter(
|
s2, _ := collections.NewSorter(
|
||||||
func(first core.Value, second core.Value) (int, error) {
|
func(first collections.DataSet, second collections.DataSet) (int, error) {
|
||||||
o1, _ := first.(*values.Object).Get("two")
|
o1, _ := first.Get(collections.DefaultValueVar).(*values.Object).Get("two")
|
||||||
o2, _ := second.(*values.Object).Get("two")
|
o2, _ := second.Get(collections.DefaultValueVar).(*values.Object).Get("two")
|
||||||
|
|
||||||
return o1.Compare(o2), nil
|
return o1.Compare(o2), nil
|
||||||
},
|
},
|
||||||
@ -120,17 +134,19 @@ func TestSort(t *testing.T) {
|
|||||||
)
|
)
|
||||||
|
|
||||||
src, err := collections.NewSortIterator(
|
src, err := collections.NewSortIterator(
|
||||||
collections.NewSliceIterator(arr),
|
sliceIterator(arr),
|
||||||
s1,
|
s1,
|
||||||
s2,
|
s2,
|
||||||
)
|
)
|
||||||
|
|
||||||
So(err, ShouldBeNil)
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
res, err := collections.ToSlice(src)
|
sets, err := collections.ToSlice(src)
|
||||||
|
|
||||||
So(err, ShouldBeNil)
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
res := toValues(sets)
|
||||||
|
|
||||||
j, _ := json.Marshal(res)
|
j, _ := json.Marshal(res)
|
||||||
|
|
||||||
So(string(j), ShouldEqual, `[{"one":1,"two":1},{"one":1,"two":2},{"one":2,"two":1},{"one":2,"two":2},{"one":3,"two":1},{"one":3,"two":2},{"one":4,"two":1},{"one":4,"two":2}]`)
|
So(string(j), ShouldEqual, `[{"one":1,"two":1},{"one":1,"two":2},{"one":2,"two":1},{"one":2,"two":2},{"one":3,"two":1},{"one":3,"two":2},{"one":4,"two":1},{"one":4,"two":2}]`)
|
||||||
@ -158,9 +174,9 @@ func TestSort(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
s1, _ := collections.NewSorter(
|
s1, _ := collections.NewSorter(
|
||||||
func(first core.Value, second core.Value) (int, error) {
|
func(first collections.DataSet, second collections.DataSet) (int, error) {
|
||||||
o1, _ := first.(*values.Object).Get("one")
|
o1, _ := first.Get(collections.DefaultValueVar).(*values.Object).Get("one")
|
||||||
o2, _ := second.(*values.Object).Get("one")
|
o2, _ := second.Get(collections.DefaultValueVar).(*values.Object).Get("one")
|
||||||
|
|
||||||
return o1.Compare(o2), nil
|
return o1.Compare(o2), nil
|
||||||
},
|
},
|
||||||
@ -168,9 +184,9 @@ func TestSort(t *testing.T) {
|
|||||||
)
|
)
|
||||||
|
|
||||||
s2, _ := collections.NewSorter(
|
s2, _ := collections.NewSorter(
|
||||||
func(first core.Value, second core.Value) (int, error) {
|
func(first collections.DataSet, second collections.DataSet) (int, error) {
|
||||||
o1, _ := first.(*values.Object).Get("two")
|
o1, _ := first.Get(collections.DefaultValueVar).(*values.Object).Get("two")
|
||||||
o2, _ := second.(*values.Object).Get("two")
|
o2, _ := second.Get(collections.DefaultValueVar).(*values.Object).Get("two")
|
||||||
|
|
||||||
return o1.Compare(o2), nil
|
return o1.Compare(o2), nil
|
||||||
},
|
},
|
||||||
@ -178,17 +194,19 @@ func TestSort(t *testing.T) {
|
|||||||
)
|
)
|
||||||
|
|
||||||
src, err := collections.NewSortIterator(
|
src, err := collections.NewSortIterator(
|
||||||
collections.NewSliceIterator(arr),
|
sliceIterator(arr),
|
||||||
s1,
|
s1,
|
||||||
s2,
|
s2,
|
||||||
)
|
)
|
||||||
|
|
||||||
So(err, ShouldBeNil)
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
res, err := collections.ToSlice(src)
|
sets, err := collections.ToSlice(src)
|
||||||
|
|
||||||
So(err, ShouldBeNil)
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
res := toValues(sets)
|
||||||
|
|
||||||
j, _ := json.Marshal(res)
|
j, _ := json.Marshal(res)
|
||||||
|
|
||||||
So(string(j), ShouldEqual, `[{"one":4,"two":2},{"one":4,"two":1},{"one":3,"two":2},{"one":3,"two":1},{"one":2,"two":2},{"one":2,"two":1},{"one":1,"two":2},{"one":1,"two":1}]`)
|
So(string(j), ShouldEqual, `[{"one":4,"two":2},{"one":4,"two":1},{"one":3,"two":2},{"one":3,"two":1},{"one":2,"two":2},{"one":2,"two":1},{"one":1,"two":2},{"one":1,"two":1}]`)
|
||||||
@ -216,9 +234,9 @@ func TestSort(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
s1, _ := collections.NewSorter(
|
s1, _ := collections.NewSorter(
|
||||||
func(first core.Value, second core.Value) (int, error) {
|
func(first collections.DataSet, second collections.DataSet) (int, error) {
|
||||||
o1, _ := first.(*values.Object).Get("one")
|
o1, _ := first.Get(collections.DefaultValueVar).(*values.Object).Get("one")
|
||||||
o2, _ := second.(*values.Object).Get("one")
|
o2, _ := second.Get(collections.DefaultValueVar).(*values.Object).Get("one")
|
||||||
|
|
||||||
return o1.Compare(o2), nil
|
return o1.Compare(o2), nil
|
||||||
},
|
},
|
||||||
@ -226,9 +244,9 @@ func TestSort(t *testing.T) {
|
|||||||
)
|
)
|
||||||
|
|
||||||
s2, _ := collections.NewSorter(
|
s2, _ := collections.NewSorter(
|
||||||
func(first core.Value, second core.Value) (int, error) {
|
func(first collections.DataSet, second collections.DataSet) (int, error) {
|
||||||
o1, _ := first.(*values.Object).Get("two")
|
o1, _ := first.Get(collections.DefaultValueVar).(*values.Object).Get("two")
|
||||||
o2, _ := second.(*values.Object).Get("two")
|
o2, _ := second.Get(collections.DefaultValueVar).(*values.Object).Get("two")
|
||||||
|
|
||||||
return o1.Compare(o2), nil
|
return o1.Compare(o2), nil
|
||||||
},
|
},
|
||||||
@ -236,17 +254,19 @@ func TestSort(t *testing.T) {
|
|||||||
)
|
)
|
||||||
|
|
||||||
src, err := collections.NewSortIterator(
|
src, err := collections.NewSortIterator(
|
||||||
collections.NewSliceIterator(arr),
|
sliceIterator(arr),
|
||||||
s1,
|
s1,
|
||||||
s2,
|
s2,
|
||||||
)
|
)
|
||||||
|
|
||||||
So(err, ShouldBeNil)
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
res, err := collections.ToSlice(src)
|
sets, err := collections.ToSlice(src)
|
||||||
|
|
||||||
So(err, ShouldBeNil)
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
res := toValues(sets)
|
||||||
|
|
||||||
j, _ := json.Marshal(res)
|
j, _ := json.Marshal(res)
|
||||||
|
|
||||||
So(string(j), ShouldEqual, `[{"one":1,"two":2},{"one":1,"two":1},{"one":2,"two":2},{"one":2,"two":1},{"one":3,"two":2},{"one":3,"two":1},{"one":4,"two":2},{"one":4,"two":1}]`)
|
So(string(j), ShouldEqual, `[{"one":1,"two":2},{"one":1,"two":1},{"one":2,"two":2},{"one":2,"two":1},{"one":3,"two":2},{"one":3,"two":1},{"one":4,"two":2},{"one":4,"two":1}]`)
|
||||||
@ -274,9 +294,9 @@ func TestSort(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
s1, _ := collections.NewSorter(
|
s1, _ := collections.NewSorter(
|
||||||
func(first core.Value, second core.Value) (int, error) {
|
func(first collections.DataSet, second collections.DataSet) (int, error) {
|
||||||
o1, _ := first.(*values.Object).Get("one")
|
o1, _ := first.Get(collections.DefaultValueVar).(*values.Object).Get("one")
|
||||||
o2, _ := second.(*values.Object).Get("one")
|
o2, _ := second.Get(collections.DefaultValueVar).(*values.Object).Get("one")
|
||||||
|
|
||||||
return o1.Compare(o2), nil
|
return o1.Compare(o2), nil
|
||||||
},
|
},
|
||||||
@ -284,9 +304,9 @@ func TestSort(t *testing.T) {
|
|||||||
)
|
)
|
||||||
|
|
||||||
s2, _ := collections.NewSorter(
|
s2, _ := collections.NewSorter(
|
||||||
func(first core.Value, second core.Value) (int, error) {
|
func(first collections.DataSet, second collections.DataSet) (int, error) {
|
||||||
o1, _ := first.(*values.Object).Get("two")
|
o1, _ := first.Get(collections.DefaultValueVar).(*values.Object).Get("two")
|
||||||
o2, _ := second.(*values.Object).Get("two")
|
o2, _ := second.Get(collections.DefaultValueVar).(*values.Object).Get("two")
|
||||||
|
|
||||||
return o1.Compare(o2), nil
|
return o1.Compare(o2), nil
|
||||||
},
|
},
|
||||||
@ -294,17 +314,19 @@ func TestSort(t *testing.T) {
|
|||||||
)
|
)
|
||||||
|
|
||||||
src, err := collections.NewSortIterator(
|
src, err := collections.NewSortIterator(
|
||||||
collections.NewSliceIterator(arr),
|
sliceIterator(arr),
|
||||||
s1,
|
s1,
|
||||||
s2,
|
s2,
|
||||||
)
|
)
|
||||||
|
|
||||||
So(err, ShouldBeNil)
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
res, err := collections.ToSlice(src)
|
sets, err := collections.ToSlice(src)
|
||||||
|
|
||||||
So(err, ShouldBeNil)
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
res := toValues(sets)
|
||||||
|
|
||||||
j, _ := json.Marshal(res)
|
j, _ := json.Marshal(res)
|
||||||
|
|
||||||
So(string(j), ShouldEqual, `[{"one":4,"two":1},{"one":4,"two":2},{"one":3,"two":1},{"one":3,"two":2},{"one":2,"two":1},{"one":2,"two":2},{"one":1,"two":1},{"one":1,"two":2}]`)
|
So(string(j), ShouldEqual, `[{"one":4,"two":1},{"one":4,"two":2},{"one":3,"two":1},{"one":3,"two":2},{"one":2,"two":1},{"one":2,"two":2},{"one":1,"two":1},{"one":1,"two":2}]`)
|
||||||
|
@ -8,13 +8,13 @@ type (
|
|||||||
UniqueIterator struct {
|
UniqueIterator struct {
|
||||||
src Iterator
|
src Iterator
|
||||||
hashes map[uint64]bool
|
hashes map[uint64]bool
|
||||||
value core.Value
|
hashKey string
|
||||||
key core.Value
|
dataSet DataSet
|
||||||
err error
|
err error
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewUniqueIterator(src Iterator) (*UniqueIterator, error) {
|
func NewUniqueIterator(src Iterator, hashKey string) (*UniqueIterator, error) {
|
||||||
if src == nil {
|
if src == nil {
|
||||||
return nil, core.Error(core.ErrMissedArgument, "source")
|
return nil, core.Error(core.ErrMissedArgument, "source")
|
||||||
}
|
}
|
||||||
@ -22,6 +22,7 @@ func NewUniqueIterator(src Iterator) (*UniqueIterator, error) {
|
|||||||
return &UniqueIterator{
|
return &UniqueIterator{
|
||||||
src: src,
|
src: src,
|
||||||
hashes: make(map[uint64]bool),
|
hashes: make(map[uint64]bool),
|
||||||
|
hashKey: hashKey,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -36,26 +37,25 @@ func (iterator *UniqueIterator) HasNext() bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
if !core.IsNil(iterator.value) {
|
if iterator.dataSet != nil {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (iterator *UniqueIterator) Next() (core.Value, core.Value, error) {
|
func (iterator *UniqueIterator) Next() (DataSet, error) {
|
||||||
return iterator.value, iterator.key, iterator.err
|
return iterator.dataSet, iterator.err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (iterator *UniqueIterator) doNext() {
|
func (iterator *UniqueIterator) doNext() {
|
||||||
// reset state
|
// reset state
|
||||||
iterator.err = nil
|
iterator.err = nil
|
||||||
iterator.value = nil
|
iterator.dataSet = nil
|
||||||
iterator.key = nil
|
|
||||||
|
|
||||||
// iterate over source until we find a non-unique item
|
// iterate over source until we find a non-unique item
|
||||||
for iterator.src.HasNext() {
|
for iterator.src.HasNext() {
|
||||||
val, key, err := iterator.src.Next()
|
ds, err := iterator.src.Next()
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
iterator.err = err
|
iterator.err = err
|
||||||
@ -63,7 +63,7 @@ func (iterator *UniqueIterator) doNext() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
h := val.Hash()
|
h := ds.Get(iterator.hashKey).Hash()
|
||||||
|
|
||||||
_, exists := iterator.hashes[h]
|
_, exists := iterator.hashes[h]
|
||||||
|
|
||||||
@ -72,8 +72,7 @@ func (iterator *UniqueIterator) doNext() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
iterator.hashes[h] = true
|
iterator.hashes[h] = true
|
||||||
iterator.key = key
|
iterator.dataSet = ds
|
||||||
iterator.value = val
|
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -24,15 +24,18 @@ func TestUniqueIterator(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
iter, err := collections.NewUniqueIterator(
|
iter, err := collections.NewUniqueIterator(
|
||||||
collections.NewSliceIterator(arr),
|
sliceIterator(arr),
|
||||||
|
collections.DefaultValueVar,
|
||||||
)
|
)
|
||||||
|
|
||||||
So(err, ShouldBeNil)
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
res, err := collections.ToArray(iter)
|
sets, err := collections.ToSlice(iter)
|
||||||
|
|
||||||
So(err, ShouldBeNil)
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
res := toArrayOfValues(sets)
|
||||||
|
|
||||||
So(res.String(), ShouldEqual, `[1,2,3,4,5,6]`)
|
So(res.String(), ShouldEqual, `[1,2,3,4,5,6]`)
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -47,15 +50,18 @@ func TestUniqueIterator(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
iter, err := collections.NewUniqueIterator(
|
iter, err := collections.NewUniqueIterator(
|
||||||
collections.NewSliceIterator(arr),
|
sliceIterator(arr),
|
||||||
|
collections.DefaultValueVar,
|
||||||
)
|
)
|
||||||
|
|
||||||
So(err, ShouldBeNil)
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
res, err := collections.ToArray(iter)
|
sets, err := collections.ToSlice(iter)
|
||||||
|
|
||||||
So(err, ShouldBeNil)
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
res := toArrayOfValues(sets)
|
||||||
|
|
||||||
So(res.String(), ShouldEqual, `[1]`)
|
So(res.String(), ShouldEqual, `[1]`)
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -75,15 +81,18 @@ func TestUniqueIterator(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
iter, err := collections.NewUniqueIterator(
|
iter, err := collections.NewUniqueIterator(
|
||||||
collections.NewSliceIterator(arr),
|
sliceIterator(arr),
|
||||||
|
collections.DefaultValueVar,
|
||||||
)
|
)
|
||||||
|
|
||||||
So(err, ShouldBeNil)
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
res, err := collections.ToArray(iter)
|
sets, err := collections.ToSlice(iter)
|
||||||
|
|
||||||
So(err, ShouldBeNil)
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
|
res := toArrayOfValues(sets)
|
||||||
|
|
||||||
So(res.String(), ShouldEqual, `["a","b","c","d","e","f"]`)
|
So(res.String(), ShouldEqual, `["a","b","c","d","e","f"]`)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -50,6 +50,10 @@ func Error(err error, msg string) error {
|
|||||||
return errors.Errorf("%s: %s", err.Error(), msg)
|
return errors.Errorf("%s: %s", err.Error(), msg)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Errorf(err error, format string, args ...interface{}) error {
|
||||||
|
return errors.Errorf("%s: %s", err.Error(), fmt.Sprintf(format, args...))
|
||||||
|
}
|
||||||
|
|
||||||
func Errors(err ...error) error {
|
func Errors(err ...error) error {
|
||||||
message := ""
|
message := ""
|
||||||
|
|
||||||
|
@ -1,33 +0,0 @@
|
|||||||
package clauses
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"github.com/MontFerret/ferret/pkg/runtime/collections"
|
|
||||||
"github.com/MontFerret/ferret/pkg/runtime/core"
|
|
||||||
"github.com/MontFerret/ferret/pkg/runtime/values"
|
|
||||||
)
|
|
||||||
|
|
||||||
type baseClause struct {
|
|
||||||
src core.SourceMap
|
|
||||||
dataSource collections.IterableExpression
|
|
||||||
}
|
|
||||||
|
|
||||||
func (clause *baseClause) Iterate(ctx context.Context, scope *core.Scope) (collections.Iterator, error) {
|
|
||||||
src, err := clause.dataSource.Iterate(ctx, scope)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return src, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (clause *baseClause) Exec(ctx context.Context, scope *core.Scope) (core.Value, error) {
|
|
||||||
iterator, err := clause.Iterate(ctx, scope)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return values.None, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return collections.ToArray(iterator)
|
|
||||||
}
|
|
170
pkg/runtime/expressions/clauses/collect.go
Normal file
170
pkg/runtime/expressions/clauses/collect.go
Normal file
@ -0,0 +1,170 @@
|
|||||||
|
package clauses
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"github.com/MontFerret/ferret/pkg/runtime/collections"
|
||||||
|
"github.com/MontFerret/ferret/pkg/runtime/core"
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
Collect struct {
|
||||||
|
group *CollectGroup
|
||||||
|
count *CollectCount
|
||||||
|
aggregate *CollectAggregate
|
||||||
|
}
|
||||||
|
|
||||||
|
CollectGroup struct {
|
||||||
|
selectors []*CollectSelector
|
||||||
|
projection *CollectProjection
|
||||||
|
count *CollectCount
|
||||||
|
aggregate *CollectAggregate
|
||||||
|
}
|
||||||
|
|
||||||
|
CollectCount struct {
|
||||||
|
variable string
|
||||||
|
}
|
||||||
|
|
||||||
|
CollectProjection struct {
|
||||||
|
selector *CollectSelector
|
||||||
|
}
|
||||||
|
|
||||||
|
CollectAggregate struct {
|
||||||
|
selectors []*CollectAggregateSelector
|
||||||
|
}
|
||||||
|
|
||||||
|
CollectClause struct {
|
||||||
|
src core.SourceMap
|
||||||
|
dataSource collections.Iterable
|
||||||
|
params *Collect
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewCollect(
|
||||||
|
selectors []*CollectSelector,
|
||||||
|
projection *CollectProjection,
|
||||||
|
count *CollectCount,
|
||||||
|
aggregate *CollectAggregate,
|
||||||
|
) (*Collect, error) {
|
||||||
|
collect := new(Collect)
|
||||||
|
|
||||||
|
// grouping
|
||||||
|
if selectors != nil {
|
||||||
|
collect.group = new(CollectGroup)
|
||||||
|
collect.group.selectors = selectors
|
||||||
|
|
||||||
|
if projection == nil && count == nil && aggregate == nil {
|
||||||
|
return collect, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if projection != nil && count == nil && aggregate == nil {
|
||||||
|
collect.group.projection = projection
|
||||||
|
} else if projection == nil && count != nil && aggregate == nil {
|
||||||
|
collect.group.count = count
|
||||||
|
} else if projection == nil && count == nil && aggregate != nil {
|
||||||
|
collect.group.aggregate = aggregate
|
||||||
|
} else {
|
||||||
|
return nil, core.Error(core.ErrInvalidOperation, "projection, count and aggregate cannot be used together")
|
||||||
|
}
|
||||||
|
|
||||||
|
return collect, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if count == nil && aggregate != nil {
|
||||||
|
collect.aggregate = aggregate
|
||||||
|
} else if count != nil && aggregate == nil {
|
||||||
|
collect.count = count
|
||||||
|
} else {
|
||||||
|
return nil, core.Error(core.ErrInvalidOperation, "count and aggregate cannot be used together")
|
||||||
|
}
|
||||||
|
|
||||||
|
return collect, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewCollectCount(variable string) (*CollectCount, error) {
|
||||||
|
if variable == "" {
|
||||||
|
return nil, core.Error(core.ErrMissedArgument, "count variable")
|
||||||
|
}
|
||||||
|
|
||||||
|
return &CollectCount{variable}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewCollectProjection(selector *CollectSelector) (*CollectProjection, error) {
|
||||||
|
if selector == nil {
|
||||||
|
return nil, core.Error(core.ErrMissedArgument, "projection selector")
|
||||||
|
}
|
||||||
|
|
||||||
|
return &CollectProjection{selector}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewCollectAggregate(selectors []*CollectAggregateSelector) (*CollectAggregate, error) {
|
||||||
|
if selectors == nil {
|
||||||
|
return nil, core.Error(core.ErrMissedArgument, "aggregate selectors")
|
||||||
|
}
|
||||||
|
|
||||||
|
return &CollectAggregate{selectors}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewCollectClause(
|
||||||
|
src core.SourceMap,
|
||||||
|
dataSource collections.Iterable,
|
||||||
|
params *Collect,
|
||||||
|
) (collections.Iterable, error) {
|
||||||
|
if dataSource == nil {
|
||||||
|
return nil, core.Error(core.ErrMissedArgument, "dataSource source")
|
||||||
|
}
|
||||||
|
|
||||||
|
return &CollectClause{src, dataSource, params}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (clause *CollectClause) Variables() collections.Variables {
|
||||||
|
vars := make(collections.Variables, 0, 10)
|
||||||
|
|
||||||
|
if clause.params.group != nil {
|
||||||
|
grouping := clause.params.group
|
||||||
|
|
||||||
|
for _, selector := range grouping.selectors {
|
||||||
|
vars = append(vars, selector.variable)
|
||||||
|
}
|
||||||
|
|
||||||
|
if grouping.projection != nil {
|
||||||
|
vars = append(vars, clause.params.group.projection.selector.variable)
|
||||||
|
}
|
||||||
|
|
||||||
|
if grouping.count != nil {
|
||||||
|
vars = append(vars, clause.params.group.count.variable)
|
||||||
|
}
|
||||||
|
|
||||||
|
if grouping.aggregate != nil {
|
||||||
|
for _, selector := range grouping.aggregate.selectors {
|
||||||
|
vars = append(vars, selector.variable)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if clause.params.count != nil {
|
||||||
|
vars = append(vars, clause.params.count.variable)
|
||||||
|
} else if clause.params.aggregate != nil {
|
||||||
|
for _, selector := range clause.params.aggregate.selectors {
|
||||||
|
vars = append(vars, selector.variable)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return vars
|
||||||
|
}
|
||||||
|
|
||||||
|
func (clause *CollectClause) Iterate(ctx context.Context, scope *core.Scope) (collections.Iterator, error) {
|
||||||
|
srcIterator, err := clause.dataSource.Iterate(ctx, scope)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, core.SourceError(clause.src, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
srcVariables := clause.dataSource.Variables()
|
||||||
|
|
||||||
|
return NewCollectIterator(
|
||||||
|
clause.src,
|
||||||
|
clause.params,
|
||||||
|
srcIterator,
|
||||||
|
srcVariables,
|
||||||
|
ctx,
|
||||||
|
scope,
|
||||||
|
)
|
||||||
|
}
|
412
pkg/runtime/expressions/clauses/collect_iterator.go
Normal file
412
pkg/runtime/expressions/clauses/collect_iterator.go
Normal file
@ -0,0 +1,412 @@
|
|||||||
|
package clauses
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"github.com/MontFerret/ferret/pkg/runtime/collections"
|
||||||
|
"github.com/MontFerret/ferret/pkg/runtime/core"
|
||||||
|
"github.com/MontFerret/ferret/pkg/runtime/values"
|
||||||
|
)
|
||||||
|
|
||||||
|
type CollectIterator struct {
|
||||||
|
ready bool
|
||||||
|
values []collections.DataSet
|
||||||
|
pos int
|
||||||
|
src core.SourceMap
|
||||||
|
params *Collect
|
||||||
|
dataSource collections.Iterator
|
||||||
|
variables collections.Variables
|
||||||
|
ctx context.Context
|
||||||
|
scope *core.Scope
|
||||||
|
}
|
||||||
|
|
||||||
|
//revive:disable context-as-argument
|
||||||
|
func NewCollectIterator(
|
||||||
|
src core.SourceMap,
|
||||||
|
params *Collect,
|
||||||
|
dataSource collections.Iterator,
|
||||||
|
variables collections.Variables,
|
||||||
|
ctx context.Context,
|
||||||
|
scope *core.Scope,
|
||||||
|
) (*CollectIterator, error) {
|
||||||
|
if params.group != nil {
|
||||||
|
if params.group.selectors != nil {
|
||||||
|
var err error
|
||||||
|
sorters := make([]*collections.Sorter, len(params.group.selectors))
|
||||||
|
|
||||||
|
for i, selector := range params.group.selectors {
|
||||||
|
sorter, err := newGroupSorter(ctx, scope, variables, selector)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
sorters[i] = sorter
|
||||||
|
}
|
||||||
|
|
||||||
|
dataSource, err = collections.NewSortIterator(dataSource, sorters...)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if params.group.count != nil && params.group.projection != nil {
|
||||||
|
return nil, core.Error(core.ErrInvalidArgumentNumber, "counter and projection cannot be used together")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &CollectIterator{
|
||||||
|
false,
|
||||||
|
nil,
|
||||||
|
0,
|
||||||
|
src,
|
||||||
|
params,
|
||||||
|
dataSource,
|
||||||
|
variables,
|
||||||
|
ctx,
|
||||||
|
scope,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func newGroupSorter(ctx context.Context, scope *core.Scope, variables collections.Variables, selector *CollectSelector) (*collections.Sorter, error) {
|
||||||
|
return collections.NewSorter(func(first collections.DataSet, second collections.DataSet) (int, error) {
|
||||||
|
scope1 := scope.Fork()
|
||||||
|
first.Apply(scope1, variables)
|
||||||
|
|
||||||
|
f, err := selector.expression.Exec(ctx, scope1)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return -1, err
|
||||||
|
}
|
||||||
|
|
||||||
|
scope2 := scope.Fork()
|
||||||
|
second.Apply(scope2, variables)
|
||||||
|
|
||||||
|
s, err := selector.expression.Exec(ctx, scope2)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return -1, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return f.Compare(s), nil
|
||||||
|
}, collections.SortDirectionAsc)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (iterator *CollectIterator) HasNext() bool {
|
||||||
|
if !iterator.ready {
|
||||||
|
iterator.ready = true
|
||||||
|
groups, err := iterator.init()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
iterator.values = nil
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
iterator.values = groups
|
||||||
|
}
|
||||||
|
|
||||||
|
return len(iterator.values) > iterator.pos
|
||||||
|
}
|
||||||
|
|
||||||
|
func (iterator *CollectIterator) Next() (collections.DataSet, error) {
|
||||||
|
if len(iterator.values) > iterator.pos {
|
||||||
|
val := iterator.values[iterator.pos]
|
||||||
|
iterator.pos++
|
||||||
|
|
||||||
|
return val, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, collections.ErrExhausted
|
||||||
|
}
|
||||||
|
|
||||||
|
func (iterator *CollectIterator) init() ([]collections.DataSet, error) {
|
||||||
|
if iterator.params.group != nil {
|
||||||
|
return iterator.group()
|
||||||
|
}
|
||||||
|
|
||||||
|
if iterator.params.count != nil {
|
||||||
|
return iterator.count()
|
||||||
|
}
|
||||||
|
|
||||||
|
if iterator.params.aggregate != nil {
|
||||||
|
return iterator.aggregate()
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, core.ErrInvalidOperation
|
||||||
|
}
|
||||||
|
|
||||||
|
func (iterator *CollectIterator) group() ([]collections.DataSet, error) {
|
||||||
|
// TODO: honestly, this code is ugly. it needs to be refactored in more chained way with much less if statements
|
||||||
|
// slice of groups
|
||||||
|
collected := make([]collections.DataSet, 0, 10)
|
||||||
|
// hash table of unique values
|
||||||
|
// key is a DataSet hash
|
||||||
|
// value is its index in result slice (collected)
|
||||||
|
hashTable := make(map[uint64]int)
|
||||||
|
ctx := iterator.ctx
|
||||||
|
|
||||||
|
groupSelectors := iterator.params.group.selectors
|
||||||
|
proj := iterator.params.group.projection
|
||||||
|
count := iterator.params.group.count
|
||||||
|
aggr := iterator.params.group.aggregate
|
||||||
|
|
||||||
|
// iterating over underlying data source
|
||||||
|
for iterator.dataSource.HasNext() {
|
||||||
|
set, err := iterator.dataSource.Next()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(set) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// creating a new scope for all further operations
|
||||||
|
childScope := iterator.scope.Fork()
|
||||||
|
|
||||||
|
// populate the new scope with results from an underlying source and its exposed variables
|
||||||
|
if err := set.Apply(childScope, iterator.variables); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// this data set represents a data of a given iteration with values retrieved by selectors
|
||||||
|
ds := collections.NewDataSet()
|
||||||
|
|
||||||
|
// iterate over each selector for a current data set
|
||||||
|
for _, selector := range groupSelectors {
|
||||||
|
// execute a selector and get a value
|
||||||
|
// e.g. COLLECT age = u.age
|
||||||
|
value, err := selector.expression.Exec(ctx, childScope)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
ds.Set(selector.variable, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
// it important to get hash value before projection and counting
|
||||||
|
// otherwise hash value will be inaccurate
|
||||||
|
h := ds.Hash()
|
||||||
|
|
||||||
|
_, exists := hashTable[h]
|
||||||
|
|
||||||
|
if !exists {
|
||||||
|
collected = append(collected, ds)
|
||||||
|
hashTable[h] = len(collected) - 1
|
||||||
|
|
||||||
|
if proj != nil {
|
||||||
|
// create a new variable for keeping projection
|
||||||
|
ds.Set(proj.selector.variable, values.NewArray(10))
|
||||||
|
} else if count != nil {
|
||||||
|
// create a new variable for keeping counter
|
||||||
|
ds.Set(count.variable, values.ZeroInt)
|
||||||
|
} else if aggr != nil {
|
||||||
|
// create a new variable for keeping aggregated values
|
||||||
|
for _, selector := range aggr.selectors {
|
||||||
|
arr := values.NewArray(len(selector.aggregators))
|
||||||
|
|
||||||
|
for range selector.aggregators {
|
||||||
|
arr.Push(values.None)
|
||||||
|
}
|
||||||
|
|
||||||
|
ds.Set(selector.variable, arr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if proj != nil {
|
||||||
|
idx := hashTable[h]
|
||||||
|
ds := collected[idx]
|
||||||
|
groupValue := ds.Get(proj.selector.variable)
|
||||||
|
|
||||||
|
arr, ok := groupValue.(*values.Array)
|
||||||
|
|
||||||
|
if !ok {
|
||||||
|
return nil, core.TypeError(groupValue.Type(), core.IntType)
|
||||||
|
}
|
||||||
|
|
||||||
|
value, err := proj.selector.expression.Exec(ctx, childScope)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
arr.Push(value)
|
||||||
|
} else if count != nil {
|
||||||
|
idx := hashTable[h]
|
||||||
|
ds := collected[idx]
|
||||||
|
groupValue := ds.Get(count.variable)
|
||||||
|
|
||||||
|
counter, ok := groupValue.(values.Int)
|
||||||
|
|
||||||
|
if !ok {
|
||||||
|
return nil, core.TypeError(groupValue.Type(), core.IntType)
|
||||||
|
}
|
||||||
|
|
||||||
|
groupValue = counter + 1
|
||||||
|
// set a new value
|
||||||
|
ds.Set(count.variable, groupValue)
|
||||||
|
} else if aggr != nil {
|
||||||
|
idx := hashTable[h]
|
||||||
|
ds := collected[idx]
|
||||||
|
|
||||||
|
// iterate over each selector for a current data set
|
||||||
|
for _, selector := range aggr.selectors {
|
||||||
|
vv := ds.Get(selector.variable).(*values.Array)
|
||||||
|
|
||||||
|
// execute a selector and get a value
|
||||||
|
// e.g. AGGREGATE age = CONCAT(u.age, u.dob)
|
||||||
|
// u.age and u.dob get executed
|
||||||
|
for idx, exp := range selector.aggregators {
|
||||||
|
arg, err := exp.Exec(ctx, childScope)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var args *values.Array
|
||||||
|
idx := values.NewInt(idx)
|
||||||
|
|
||||||
|
if vv.Get(idx) == values.None {
|
||||||
|
args = values.NewArray(10)
|
||||||
|
vv.Set(idx, args)
|
||||||
|
} else {
|
||||||
|
args = vv.Get(idx).(*values.Array)
|
||||||
|
}
|
||||||
|
|
||||||
|
args.Push(arg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if aggr != nil {
|
||||||
|
for _, ds := range collected {
|
||||||
|
for _, selector := range aggr.selectors {
|
||||||
|
arr := ds[selector.variable].(*values.Array)
|
||||||
|
|
||||||
|
matrix := make([]core.Value, arr.Length())
|
||||||
|
|
||||||
|
arr.ForEach(func(value core.Value, idx int) bool {
|
||||||
|
matrix[idx] = value
|
||||||
|
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
|
||||||
|
reduced, err := selector.reducer(ctx, matrix...)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// replace value with calculated one
|
||||||
|
ds.Set(selector.variable, reduced)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return collected, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (iterator *CollectIterator) count() ([]collections.DataSet, error) {
|
||||||
|
var counter int
|
||||||
|
|
||||||
|
// iterating over underlying data source
|
||||||
|
for iterator.dataSource.HasNext() {
|
||||||
|
_, err := iterator.dataSource.Next()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
counter++
|
||||||
|
}
|
||||||
|
|
||||||
|
return []collections.DataSet{
|
||||||
|
{
|
||||||
|
iterator.params.count.variable: values.NewInt(counter),
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (iterator *CollectIterator) aggregate() ([]collections.DataSet, error) {
|
||||||
|
ds := collections.NewDataSet()
|
||||||
|
// matrix of aggregated expressions
|
||||||
|
// string key of the map is a selector variable
|
||||||
|
// value of the map is a matrix of arguments
|
||||||
|
// e.g. x = CONCAT(arg1, arg2, argN...)
|
||||||
|
// x is a string key where a nested array is an array of all values of argN expressions
|
||||||
|
aggregated := make(map[string][]core.Value)
|
||||||
|
ctx := iterator.ctx
|
||||||
|
selectors := iterator.params.aggregate.selectors
|
||||||
|
|
||||||
|
// iterating over underlying data source
|
||||||
|
for iterator.dataSource.HasNext() {
|
||||||
|
set, err := iterator.dataSource.Next()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(set) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// creating a new scope for all further operations
|
||||||
|
childScope := iterator.scope.Fork()
|
||||||
|
|
||||||
|
// populate the new scope with results from an underlying source and its exposed variables
|
||||||
|
if err := set.Apply(childScope, iterator.variables); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// iterate over each selector for a current data set
|
||||||
|
for _, selector := range selectors {
|
||||||
|
vv, exists := aggregated[selector.variable]
|
||||||
|
|
||||||
|
if !exists {
|
||||||
|
vv = make([]core.Value, len(selector.aggregators))
|
||||||
|
aggregated[selector.variable] = vv
|
||||||
|
}
|
||||||
|
|
||||||
|
// execute a selector and get a value
|
||||||
|
// e.g. AGGREGATE age = CONCAT(u.age, u.dob)
|
||||||
|
// u.age and u.dob get executed
|
||||||
|
for idx, exp := range selector.aggregators {
|
||||||
|
arg, err := exp.Exec(ctx, childScope)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var args *values.Array
|
||||||
|
|
||||||
|
if vv[idx] == nil {
|
||||||
|
args = values.NewArray(10)
|
||||||
|
vv[idx] = args
|
||||||
|
} else {
|
||||||
|
args = vv[idx].(*values.Array)
|
||||||
|
}
|
||||||
|
|
||||||
|
args.Push(arg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, selector := range selectors {
|
||||||
|
matrix := aggregated[selector.variable]
|
||||||
|
|
||||||
|
reduced, err := selector.reducer(ctx, matrix...)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
ds.Set(selector.variable, reduced)
|
||||||
|
}
|
||||||
|
|
||||||
|
return []collections.DataSet{ds}, nil
|
||||||
|
}
|
64
pkg/runtime/expressions/clauses/collect_selector.go
Normal file
64
pkg/runtime/expressions/clauses/collect_selector.go
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
package clauses
|
||||||
|
|
||||||
|
import "github.com/MontFerret/ferret/pkg/runtime/core"
|
||||||
|
|
||||||
|
type (
|
||||||
|
CollectSelector struct {
|
||||||
|
variable string
|
||||||
|
expression core.Expression
|
||||||
|
}
|
||||||
|
|
||||||
|
CollectAggregateSelector struct {
|
||||||
|
variable string
|
||||||
|
aggregators []core.Expression
|
||||||
|
reducer core.Function
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewCollectSelector(variable string, exp core.Expression) (*CollectSelector, error) {
|
||||||
|
if variable == "" {
|
||||||
|
return nil, core.Error(core.ErrMissedArgument, "selector variable")
|
||||||
|
}
|
||||||
|
|
||||||
|
if exp == nil {
|
||||||
|
return nil, core.Error(core.ErrMissedArgument, "selector reducer")
|
||||||
|
}
|
||||||
|
|
||||||
|
return &CollectSelector{variable, exp}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (selector *CollectSelector) Variable() string {
|
||||||
|
return selector.variable
|
||||||
|
}
|
||||||
|
|
||||||
|
func (selector *CollectSelector) Expression() core.Expression {
|
||||||
|
return selector.expression
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewCollectAggregateSelector(variable string, aggr []core.Expression, reducer core.Function) (*CollectAggregateSelector, error) {
|
||||||
|
if variable == "" {
|
||||||
|
return nil, core.Error(core.ErrMissedArgument, "selector variable")
|
||||||
|
}
|
||||||
|
|
||||||
|
if reducer == nil {
|
||||||
|
return nil, core.Error(core.ErrMissedArgument, "selector reducer")
|
||||||
|
}
|
||||||
|
|
||||||
|
if aggr == nil {
|
||||||
|
return nil, core.Error(core.ErrMissedArgument, "selector aggregators")
|
||||||
|
}
|
||||||
|
|
||||||
|
return &CollectAggregateSelector{variable, aggr, reducer}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (selector *CollectAggregateSelector) Variable() string {
|
||||||
|
return selector.variable
|
||||||
|
}
|
||||||
|
|
||||||
|
func (selector *CollectAggregateSelector) Expression() core.Function {
|
||||||
|
return selector.reducer
|
||||||
|
}
|
||||||
|
|
||||||
|
func (selector *CollectAggregateSelector) Aggregators() []core.Expression {
|
||||||
|
return selector.aggregators
|
||||||
|
}
|
@ -1,28 +0,0 @@
|
|||||||
package clauses
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"github.com/MontFerret/ferret/pkg/runtime/collections"
|
|
||||||
"github.com/MontFerret/ferret/pkg/runtime/core"
|
|
||||||
)
|
|
||||||
|
|
||||||
type DistinctClause struct {
|
|
||||||
*baseClause
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewDistinctClause(
|
|
||||||
src core.SourceMap,
|
|
||||||
dataSource collections.IterableExpression,
|
|
||||||
) *DistinctClause {
|
|
||||||
return &DistinctClause{&baseClause{src, dataSource}}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (clause *DistinctClause) Iterate(ctx context.Context, scope *core.Scope) (collections.Iterator, error) {
|
|
||||||
src, err := clause.dataSource.Iterate(ctx, scope)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return collections.NewUniqueIterator(src)
|
|
||||||
}
|
|
@ -8,25 +8,32 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type FilterClause struct {
|
type FilterClause struct {
|
||||||
*baseClause
|
src core.SourceMap
|
||||||
valVar string
|
dataSource collections.Iterable
|
||||||
keyVar string
|
|
||||||
predicate core.Expression
|
predicate core.Expression
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewFilterClause(
|
func NewFilterClause(
|
||||||
src core.SourceMap,
|
src core.SourceMap,
|
||||||
dataSource collections.IterableExpression,
|
dataSource collections.Iterable,
|
||||||
valVar string,
|
|
||||||
keyVar string,
|
|
||||||
predicate core.Expression,
|
predicate core.Expression,
|
||||||
) *FilterClause {
|
) (collections.Iterable, error) {
|
||||||
return &FilterClause{
|
if dataSource == nil {
|
||||||
&baseClause{src, dataSource},
|
return nil, core.Error(core.ErrMissedArgument, "dataSource source")
|
||||||
valVar,
|
|
||||||
keyVar,
|
|
||||||
predicate,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if predicate == nil {
|
||||||
|
return nil, core.Error(core.ErrMissedArgument, "predicate")
|
||||||
|
}
|
||||||
|
|
||||||
|
return &FilterClause{
|
||||||
|
src, dataSource,
|
||||||
|
predicate,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (clause *FilterClause) Variables() collections.Variables {
|
||||||
|
return clause.dataSource.Variables()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (clause *FilterClause) Iterate(ctx context.Context, scope *core.Scope) (collections.Iterator, error) {
|
func (clause *FilterClause) Iterate(ctx context.Context, scope *core.Scope) (collections.Iterator, error) {
|
||||||
@ -36,13 +43,15 @@ func (clause *FilterClause) Iterate(ctx context.Context, scope *core.Scope) (col
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return collections.NewFilterIterator(src, func(val core.Value, key core.Value) (bool, error) {
|
variables := clause.dataSource.Variables()
|
||||||
|
|
||||||
|
return collections.NewFilterIterator(src, func(set collections.DataSet) (bool, error) {
|
||||||
innerScope := scope.Fork()
|
innerScope := scope.Fork()
|
||||||
|
|
||||||
innerScope.SetVariable(clause.valVar, val)
|
err := set.Apply(innerScope, variables)
|
||||||
|
|
||||||
if clause.keyVar != "" {
|
if err != nil {
|
||||||
innerScope.SetVariable(clause.keyVar, key)
|
return false, core.SourceError(clause.src, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
ret, err := clause.predicate.Exec(ctx, innerScope)
|
ret, err := clause.predicate.Exec(ctx, innerScope)
|
||||||
|
@ -7,26 +7,45 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type LimitClause struct {
|
type LimitClause struct {
|
||||||
*baseClause
|
src core.SourceMap
|
||||||
|
dataSource collections.Iterable
|
||||||
count int
|
count int
|
||||||
offset int
|
offset int
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewLimitClause(
|
func NewLimitClause(
|
||||||
src core.SourceMap,
|
src core.SourceMap,
|
||||||
dataSource collections.IterableExpression,
|
dataSource collections.Iterable,
|
||||||
count int,
|
count int,
|
||||||
offset int,
|
offset int,
|
||||||
) *LimitClause {
|
) (collections.Iterable, error) {
|
||||||
return &LimitClause{&baseClause{src, dataSource}, count, offset}
|
if dataSource == nil {
|
||||||
|
return nil, core.Error(core.ErrMissedArgument, "dataSource source")
|
||||||
|
}
|
||||||
|
|
||||||
|
return &LimitClause{src, dataSource, count, offset}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (clause *LimitClause) Variables() collections.Variables {
|
||||||
|
return clause.dataSource.Variables()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (clause *LimitClause) Iterate(ctx context.Context, scope *core.Scope) (collections.Iterator, error) {
|
func (clause *LimitClause) Iterate(ctx context.Context, scope *core.Scope) (collections.Iterator, error) {
|
||||||
src, err := clause.dataSource.Iterate(ctx, scope)
|
src, err := clause.dataSource.Iterate(ctx, scope)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, core.SourceError(clause.src, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return collections.NewLimitIterator(src, clause.count, clause.offset)
|
iterator, err := collections.NewLimitIterator(
|
||||||
|
src,
|
||||||
|
clause.count,
|
||||||
|
clause.offset,
|
||||||
|
)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, core.SourceError(clause.src, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return iterator, nil
|
||||||
}
|
}
|
||||||
|
@ -12,15 +12,15 @@ type (
|
|||||||
direction collections.SortDirection
|
direction collections.SortDirection
|
||||||
}
|
}
|
||||||
SortClause struct {
|
SortClause struct {
|
||||||
*baseClause
|
src core.SourceMap
|
||||||
variableName string
|
dataSource collections.Iterable
|
||||||
sorters []*SorterExpression
|
sorters []*SorterExpression
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewSorterExpression(expression core.Expression, direction collections.SortDirection) (*SorterExpression, error) {
|
func NewSorterExpression(expression core.Expression, direction collections.SortDirection) (*SorterExpression, error) {
|
||||||
if expression == nil {
|
if expression == nil {
|
||||||
return nil, core.Error(core.ErrMissedArgument, "expression")
|
return nil, core.Error(core.ErrMissedArgument, "reducer")
|
||||||
}
|
}
|
||||||
|
|
||||||
if !collections.IsValidSortDirection(direction) {
|
if !collections.IsValidSortDirection(direction) {
|
||||||
@ -32,11 +32,22 @@ func NewSorterExpression(expression core.Expression, direction collections.SortD
|
|||||||
|
|
||||||
func NewSortClause(
|
func NewSortClause(
|
||||||
src core.SourceMap,
|
src core.SourceMap,
|
||||||
dataSource collections.IterableExpression,
|
dataSource collections.Iterable,
|
||||||
variableName string,
|
|
||||||
sorters ...*SorterExpression,
|
sorters ...*SorterExpression,
|
||||||
) *SortClause {
|
) (collections.Iterable, error) {
|
||||||
return &SortClause{&baseClause{src, dataSource}, variableName, sorters}
|
if dataSource == nil {
|
||||||
|
return nil, core.Error(core.ErrMissedArgument, "dataSource source")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(sorters) == 0 {
|
||||||
|
return nil, core.Error(core.ErrMissedArgument, "sorters")
|
||||||
|
}
|
||||||
|
|
||||||
|
return &SortClause{src, dataSource, sorters}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (clause *SortClause) Variables() collections.Variables {
|
||||||
|
return clause.dataSource.Variables()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (clause *SortClause) Iterate(ctx context.Context, scope *core.Scope) (collections.Iterator, error) {
|
func (clause *SortClause) Iterate(ctx context.Context, scope *core.Scope) (collections.Iterator, error) {
|
||||||
@ -47,12 +58,13 @@ func (clause *SortClause) Iterate(ctx context.Context, scope *core.Scope) (colle
|
|||||||
}
|
}
|
||||||
|
|
||||||
sorters := make([]*collections.Sorter, len(clause.sorters))
|
sorters := make([]*collections.Sorter, len(clause.sorters))
|
||||||
|
variables := clause.dataSource.Variables()
|
||||||
|
|
||||||
// converting sorter expression into collections.Sorter
|
// converting sorter reducer into collections.Sorter
|
||||||
for idx, srt := range clause.sorters {
|
for idx, srt := range clause.sorters {
|
||||||
sorter, err := collections.NewSorter(func(first core.Value, second core.Value) (int, error) {
|
sorter, err := collections.NewSorter(func(first collections.DataSet, second collections.DataSet) (int, error) {
|
||||||
scope1 := scope.Fork()
|
scope1 := scope.Fork()
|
||||||
scope1.SetVariable(clause.variableName, first)
|
first.Apply(scope1, variables)
|
||||||
|
|
||||||
f, err := srt.expression.Exec(ctx, scope1)
|
f, err := srt.expression.Exec(ctx, scope1)
|
||||||
|
|
||||||
@ -61,7 +73,7 @@ func (clause *SortClause) Iterate(ctx context.Context, scope *core.Scope) (colle
|
|||||||
}
|
}
|
||||||
|
|
||||||
scope2 := scope.Fork()
|
scope2 := scope.Fork()
|
||||||
scope2.SetVariable(clause.variableName, second)
|
second.Apply(scope2, variables)
|
||||||
|
|
||||||
s, err := srt.expression.Exec(ctx, scope2)
|
s, err := srt.expression.Exec(ctx, scope2)
|
||||||
|
|
||||||
|
71
pkg/runtime/expressions/data_source.go
Normal file
71
pkg/runtime/expressions/data_source.go
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
package expressions
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"github.com/MontFerret/ferret/pkg/runtime/collections"
|
||||||
|
"github.com/MontFerret/ferret/pkg/runtime/core"
|
||||||
|
"github.com/MontFerret/ferret/pkg/runtime/values"
|
||||||
|
)
|
||||||
|
|
||||||
|
type DataSource struct {
|
||||||
|
src core.SourceMap
|
||||||
|
variables collections.Variables
|
||||||
|
exp core.Expression
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewDataSource(
|
||||||
|
src core.SourceMap,
|
||||||
|
valVariable,
|
||||||
|
keyVariable string,
|
||||||
|
exp core.Expression,
|
||||||
|
) (collections.Iterable, error) {
|
||||||
|
if exp == nil {
|
||||||
|
return nil, core.Error(core.ErrMissedArgument, "expression")
|
||||||
|
}
|
||||||
|
|
||||||
|
return &DataSource{
|
||||||
|
src,
|
||||||
|
collections.Variables{valVariable, keyVariable},
|
||||||
|
exp,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ds *DataSource) Variables() collections.Variables {
|
||||||
|
return ds.variables
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ds *DataSource) Iterate(ctx context.Context, scope *core.Scope) (collections.Iterator, error) {
|
||||||
|
data, err := ds.exp.Exec(ctx, scope)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, core.SourceError(ds.src, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
valVar := ds.variables[0]
|
||||||
|
keyVar := ds.variables[1]
|
||||||
|
|
||||||
|
switch data.Type() {
|
||||||
|
case core.ArrayType:
|
||||||
|
return collections.NewIndexedIterator(valVar, keyVar, data.(collections.IndexedCollection)), nil
|
||||||
|
case core.ObjectType:
|
||||||
|
return collections.NewKeyedIterator(valVar, keyVar, data.(collections.KeyedCollection)), nil
|
||||||
|
case core.HTMLElementType, core.HTMLDocumentType:
|
||||||
|
return collections.NewHTMLNodeIterator(valVar, keyVar, data.(values.HTMLNode)), nil
|
||||||
|
default:
|
||||||
|
// fallback to user defined types
|
||||||
|
switch data.(type) {
|
||||||
|
case collections.KeyedCollection:
|
||||||
|
return collections.NewIndexedIterator(valVar, keyVar, data.(collections.IndexedCollection)), nil
|
||||||
|
case collections.IndexedCollection:
|
||||||
|
return collections.NewKeyedIterator(valVar, keyVar, data.(collections.KeyedCollection)), nil
|
||||||
|
default:
|
||||||
|
return nil, core.TypeError(
|
||||||
|
data.Type(),
|
||||||
|
core.ArrayType,
|
||||||
|
core.ObjectType,
|
||||||
|
core.HTMLDocumentType,
|
||||||
|
core.HTMLElementType,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -11,25 +11,19 @@ import (
|
|||||||
|
|
||||||
type ForExpression struct {
|
type ForExpression struct {
|
||||||
src core.SourceMap
|
src core.SourceMap
|
||||||
valVar string
|
dataSource collections.Iterable
|
||||||
keyVar string
|
|
||||||
dataSource collections.IterableExpression
|
|
||||||
predicate core.Expression
|
predicate core.Expression
|
||||||
|
distinct bool
|
||||||
spread bool
|
spread bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewForExpression(
|
func NewForExpression(
|
||||||
src core.SourceMap,
|
src core.SourceMap,
|
||||||
valVar string,
|
dataSource collections.Iterable,
|
||||||
keyVar string,
|
|
||||||
dataSource collections.IterableExpression,
|
|
||||||
predicate core.Expression,
|
predicate core.Expression,
|
||||||
|
distinct,
|
||||||
spread bool,
|
spread bool,
|
||||||
) (*ForExpression, error) {
|
) (*ForExpression, error) {
|
||||||
if valVar == "" {
|
|
||||||
return nil, errors.Wrap(core.ErrInvalidArgument, "valVar is empty")
|
|
||||||
}
|
|
||||||
|
|
||||||
if core.IsNil(dataSource) {
|
if core.IsNil(dataSource) {
|
||||||
return nil, errors.Wrap(core.ErrMissedArgument, "missed source expression")
|
return nil, errors.Wrap(core.ErrMissedArgument, "missed source expression")
|
||||||
}
|
}
|
||||||
@ -40,27 +34,59 @@ func NewForExpression(
|
|||||||
|
|
||||||
return &ForExpression{
|
return &ForExpression{
|
||||||
src,
|
src,
|
||||||
valVar, keyVar,
|
|
||||||
dataSource,
|
dataSource,
|
||||||
predicate,
|
predicate,
|
||||||
|
distinct,
|
||||||
spread,
|
spread,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *ForExpression) AddLimit(src core.SourceMap, size, count int) {
|
func (e *ForExpression) AddLimit(src core.SourceMap, size, count int) error {
|
||||||
e.dataSource = clauses.NewLimitClause(src, e.dataSource, size, count)
|
limit, err := clauses.NewLimitClause(src, e.dataSource, size, count)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
e.dataSource = limit
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *ForExpression) AddFilter(src core.SourceMap, exp core.Expression) {
|
func (e *ForExpression) AddFilter(src core.SourceMap, exp core.Expression) error {
|
||||||
e.dataSource = clauses.NewFilterClause(src, e.dataSource, e.valVar, e.keyVar, exp)
|
filter, err := clauses.NewFilterClause(src, e.dataSource, exp)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
e.dataSource = filter
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *ForExpression) AddSort(src core.SourceMap, sorters ...*clauses.SorterExpression) {
|
func (e *ForExpression) AddSort(src core.SourceMap, sorters ...*clauses.SorterExpression) error {
|
||||||
e.dataSource = clauses.NewSortClause(src, e.dataSource, e.valVar, sorters...)
|
sort, err := clauses.NewSortClause(src, e.dataSource, sorters...)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
e.dataSource = sort
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *ForExpression) AddDistinct(src core.SourceMap) {
|
func (e *ForExpression) AddCollect(src core.SourceMap, params *clauses.Collect) error {
|
||||||
e.dataSource = clauses.NewDistinctClause(src, e.dataSource)
|
collect, err := clauses.NewCollectClause(src, e.dataSource, params)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
e.dataSource = collect
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *ForExpression) Exec(ctx context.Context, scope *core.Scope) (core.Value, error) {
|
func (e *ForExpression) Exec(ctx context.Context, scope *core.Scope) (core.Value, error) {
|
||||||
@ -70,20 +96,27 @@ func (e *ForExpression) Exec(ctx context.Context, scope *core.Scope) (core.Value
|
|||||||
return values.None, err
|
return values.None, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Hash map for a check for uniqueness
|
||||||
|
var hashTable map[uint64]bool
|
||||||
|
|
||||||
|
if e.distinct {
|
||||||
|
hashTable = make(map[uint64]bool)
|
||||||
|
}
|
||||||
|
|
||||||
res := values.NewArray(10)
|
res := values.NewArray(10)
|
||||||
|
variables := e.dataSource.Variables()
|
||||||
|
|
||||||
for iterator.HasNext() {
|
for iterator.HasNext() {
|
||||||
val, key, err := iterator.Next()
|
ds, err := iterator.Next()
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return values.None, core.SourceError(e.src, err)
|
return values.None, core.SourceError(e.src, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
innerScope := scope.Fork()
|
innerScope := scope.Fork()
|
||||||
innerScope.SetVariable(e.valVar, val)
|
|
||||||
|
|
||||||
if e.keyVar != "" {
|
if err := ds.Apply(innerScope, variables); err != nil {
|
||||||
innerScope.SetVariable(e.keyVar, key)
|
return values.None, err
|
||||||
}
|
}
|
||||||
|
|
||||||
out, err := e.predicate.Exec(ctx, innerScope)
|
out, err := e.predicate.Exec(ctx, innerScope)
|
||||||
@ -92,6 +125,24 @@ func (e *ForExpression) Exec(ctx context.Context, scope *core.Scope) (core.Value
|
|||||||
return values.None, err
|
return values.None, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var add bool
|
||||||
|
|
||||||
|
// The result shouldn't be distinct
|
||||||
|
// Just add the output
|
||||||
|
if !e.distinct {
|
||||||
|
add = true
|
||||||
|
} else {
|
||||||
|
// We need to check whether the value already exists in the result set
|
||||||
|
hash := out.Hash()
|
||||||
|
_, exists := hashTable[hash]
|
||||||
|
|
||||||
|
if !exists {
|
||||||
|
hashTable[hash] = true
|
||||||
|
add = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if add {
|
||||||
if !e.spread {
|
if !e.spread {
|
||||||
res.Push(out)
|
res.Push(out)
|
||||||
} else {
|
} else {
|
||||||
@ -108,6 +159,7 @@ func (e *ForExpression) Exec(ctx context.Context, scope *core.Scope) (core.Value
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return res, nil
|
return res, nil
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,6 @@ package expressions
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"github.com/MontFerret/ferret/pkg/runtime/collections"
|
|
||||||
"github.com/MontFerret/ferret/pkg/runtime/core"
|
"github.com/MontFerret/ferret/pkg/runtime/core"
|
||||||
"github.com/MontFerret/ferret/pkg/runtime/values"
|
"github.com/MontFerret/ferret/pkg/runtime/values"
|
||||||
)
|
)
|
||||||
@ -25,20 +24,12 @@ func NewFunctionCallExpression(
|
|||||||
return &FunctionCallExpression{src, fun, args}, nil
|
return &FunctionCallExpression{src, fun, args}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *FunctionCallExpression) Iterate(ctx context.Context, scope *core.Scope) (collections.Iterator, error) {
|
func (e *FunctionCallExpression) Arguments() []core.Expression {
|
||||||
value, err := e.Exec(ctx, scope)
|
return e.args
|
||||||
|
}
|
||||||
|
|
||||||
if err != nil {
|
func (e *FunctionCallExpression) Function() core.Function {
|
||||||
return nil, core.SourceError(e.src, err)
|
return e.fun
|
||||||
}
|
|
||||||
|
|
||||||
iter, err := collections.ToIterator(value)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return nil, core.SourceError(e.src, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return iter, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *FunctionCallExpression) Exec(ctx context.Context, scope *core.Scope) (core.Value, error) {
|
func (e *FunctionCallExpression) Exec(ctx context.Context, scope *core.Scope) (core.Value, error) {
|
||||||
|
@ -2,7 +2,6 @@ package literals
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"github.com/MontFerret/ferret/pkg/runtime/collections"
|
|
||||||
"github.com/MontFerret/ferret/pkg/runtime/core"
|
"github.com/MontFerret/ferret/pkg/runtime/core"
|
||||||
"github.com/MontFerret/ferret/pkg/runtime/values"
|
"github.com/MontFerret/ferret/pkg/runtime/values"
|
||||||
)
|
)
|
||||||
@ -23,34 +22,14 @@ func (l *ArrayLiteral) Push(expression core.Expression) {
|
|||||||
l.elements = append(l.elements, expression)
|
l.elements = append(l.elements, expression)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *ArrayLiteral) Iterate(ctx context.Context, scope *core.Scope) (collections.Iterator, error) {
|
|
||||||
arr, err := l.doExec(ctx, scope)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return collections.NewArrayIterator(arr), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *ArrayLiteral) Exec(ctx context.Context, scope *core.Scope) (core.Value, error) {
|
func (l *ArrayLiteral) Exec(ctx context.Context, scope *core.Scope) (core.Value, error) {
|
||||||
arr, err := l.doExec(ctx, scope)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return values.None, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return arr, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *ArrayLiteral) doExec(ctx context.Context, scope *core.Scope) (*values.Array, error) {
|
|
||||||
arr := values.NewArray(len(l.elements))
|
arr := values.NewArray(len(l.elements))
|
||||||
|
|
||||||
for _, el := range l.elements {
|
for _, el := range l.elements {
|
||||||
val, err := el.Exec(ctx, scope)
|
val, err := el.Exec(ctx, scope)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return values.None, err
|
||||||
}
|
}
|
||||||
|
|
||||||
arr.Push(val)
|
arr.Push(val)
|
||||||
|
@ -2,7 +2,6 @@ package literals
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"github.com/MontFerret/ferret/pkg/runtime/collections"
|
|
||||||
"github.com/MontFerret/ferret/pkg/runtime/core"
|
"github.com/MontFerret/ferret/pkg/runtime/core"
|
||||||
"github.com/MontFerret/ferret/pkg/runtime/values"
|
"github.com/MontFerret/ferret/pkg/runtime/values"
|
||||||
)
|
)
|
||||||
@ -18,56 +17,40 @@ type (
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewObjectPropertyAssignment(name, value core.Expression) *ObjectPropertyAssignment {
|
func NewObjectPropertyAssignment(name, value core.Expression) (*ObjectPropertyAssignment, error) {
|
||||||
return &ObjectPropertyAssignment{name, value}
|
if name == nil {
|
||||||
}
|
return nil, core.Error(core.ErrMissedArgument, "property name expression")
|
||||||
|
}
|
||||||
|
|
||||||
func NewObjectLiteral() *ObjectLiteral {
|
if value == nil {
|
||||||
return &ObjectLiteral{make([]*ObjectPropertyAssignment, 0, 10)}
|
return nil, core.Error(core.ErrMissedArgument, "property value expression")
|
||||||
|
}
|
||||||
|
|
||||||
|
return &ObjectPropertyAssignment{name, value}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewObjectLiteralWith(props ...*ObjectPropertyAssignment) *ObjectLiteral {
|
func NewObjectLiteralWith(props ...*ObjectPropertyAssignment) *ObjectLiteral {
|
||||||
return &ObjectLiteral{props}
|
return &ObjectLiteral{props}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *ObjectLiteral) Iterate(ctx context.Context, scope *core.Scope) (collections.Iterator, error) {
|
|
||||||
obj, err := l.doExec(ctx, scope)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return collections.NewObjectIterator(obj), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *ObjectLiteral) Exec(ctx context.Context, scope *core.Scope) (core.Value, error) {
|
func (l *ObjectLiteral) Exec(ctx context.Context, scope *core.Scope) (core.Value, error) {
|
||||||
arr, err := l.doExec(ctx, scope)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return values.None, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return arr, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *ObjectLiteral) doExec(ctx context.Context, scope *core.Scope) (*values.Object, error) {
|
|
||||||
obj := values.NewObject()
|
obj := values.NewObject()
|
||||||
|
|
||||||
for _, el := range l.properties {
|
for _, el := range l.properties {
|
||||||
name, err := el.name.Exec(ctx, scope)
|
name, err := el.name.Exec(ctx, scope)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return values.None, err
|
||||||
}
|
}
|
||||||
|
|
||||||
val, err := el.value.Exec(ctx, scope)
|
val, err := el.value.Exec(ctx, scope)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return values.None, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if name.Type() != core.StringType {
|
if name.Type() != core.StringType {
|
||||||
return nil, core.TypeError(name.Type(), core.StringType)
|
return values.None, core.TypeError(name.Type(), core.StringType)
|
||||||
}
|
}
|
||||||
|
|
||||||
obj.Set(name.(values.String), val)
|
obj.Set(name.(values.String), val)
|
||||||
|
@ -2,7 +2,6 @@ package expressions
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"github.com/MontFerret/ferret/pkg/runtime/collections"
|
|
||||||
"github.com/MontFerret/ferret/pkg/runtime/core"
|
"github.com/MontFerret/ferret/pkg/runtime/core"
|
||||||
"github.com/MontFerret/ferret/pkg/runtime/values"
|
"github.com/MontFerret/ferret/pkg/runtime/values"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
@ -26,22 +25,6 @@ func NewMemberExpression(src core.SourceMap, variableName string, path []core.Ex
|
|||||||
return &MemberExpression{src, variableName, path}, nil
|
return &MemberExpression{src, variableName, path}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *MemberExpression) Iterate(ctx context.Context, scope *core.Scope) (collections.Iterator, error) {
|
|
||||||
value, err := e.Exec(ctx, scope)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return nil, core.SourceError(e.src, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
iter, err := collections.ToIterator(value)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return nil, core.SourceError(e.src, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return iter, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *MemberExpression) Exec(ctx context.Context, scope *core.Scope) (core.Value, error) {
|
func (e *MemberExpression) Exec(ctx context.Context, scope *core.Scope) (core.Value, error) {
|
||||||
val, err := scope.GetVariable(e.variableName)
|
val, err := scope.GetVariable(e.variableName)
|
||||||
|
|
||||||
|
@ -2,7 +2,6 @@ package operators
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"github.com/MontFerret/ferret/pkg/runtime/collections"
|
|
||||||
"github.com/MontFerret/ferret/pkg/runtime/core"
|
"github.com/MontFerret/ferret/pkg/runtime/core"
|
||||||
"github.com/MontFerret/ferret/pkg/runtime/values"
|
"github.com/MontFerret/ferret/pkg/runtime/values"
|
||||||
)
|
)
|
||||||
@ -27,16 +26,6 @@ func NewRangeOperator(
|
|||||||
return &RangeOperator{&baseOperator{src, left, right}}, nil
|
return &RangeOperator{&baseOperator{src, left, right}}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (operator *RangeOperator) Iterate(ctx context.Context, scope *core.Scope) (collections.Iterator, error) {
|
|
||||||
arr, err := operator.Exec(ctx, scope)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return collections.NewArrayIterator(arr.(*values.Array)), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (operator *RangeOperator) Exec(ctx context.Context, scope *core.Scope) (core.Value, error) {
|
func (operator *RangeOperator) Exec(ctx context.Context, scope *core.Scope) (core.Value, error) {
|
||||||
left, err := operator.left.Exec(ctx, scope)
|
left, err := operator.left.Exec(ctx, scope)
|
||||||
|
|
||||||
|
@ -2,7 +2,6 @@ package expressions
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"github.com/MontFerret/ferret/pkg/runtime/collections"
|
|
||||||
"github.com/MontFerret/ferret/pkg/runtime/core"
|
"github.com/MontFerret/ferret/pkg/runtime/core"
|
||||||
"github.com/MontFerret/ferret/pkg/runtime/values"
|
"github.com/MontFerret/ferret/pkg/runtime/values"
|
||||||
)
|
)
|
||||||
@ -20,22 +19,6 @@ func NewParameterExpression(src core.SourceMap, name string) (*ParameterExpressi
|
|||||||
return &ParameterExpression{src, name}, nil
|
return &ParameterExpression{src, name}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *ParameterExpression) Iterate(ctx context.Context, scope *core.Scope) (collections.Iterator, error) {
|
|
||||||
value, err := e.Exec(ctx, scope)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return nil, core.SourceError(e.src, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
iter, err := collections.ToIterator(value)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return nil, core.SourceError(e.src, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return iter, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *ParameterExpression) Exec(ctx context.Context, _ *core.Scope) (core.Value, error) {
|
func (e *ParameterExpression) Exec(ctx context.Context, _ *core.Scope) (core.Value, error) {
|
||||||
param, err := core.ParamFrom(ctx, e.name)
|
param, err := core.ParamFrom(ctx, e.name)
|
||||||
|
|
||||||
|
@ -2,7 +2,6 @@ package expressions
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"github.com/MontFerret/ferret/pkg/runtime/collections"
|
|
||||||
"github.com/MontFerret/ferret/pkg/runtime/core"
|
"github.com/MontFerret/ferret/pkg/runtime/core"
|
||||||
"github.com/MontFerret/ferret/pkg/runtime/values"
|
"github.com/MontFerret/ferret/pkg/runtime/values"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
@ -42,22 +41,6 @@ func NewVariableDeclarationExpression(src core.SourceMap, name string, init core
|
|||||||
return &VariableDeclarationExpression{v, init}, nil
|
return &VariableDeclarationExpression{v, init}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *VariableExpression) Iterate(ctx context.Context, scope *core.Scope) (collections.Iterator, error) {
|
|
||||||
value, err := e.Exec(ctx, scope)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return nil, core.SourceError(e.src, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
iter, err := collections.ToIterator(value)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return nil, core.SourceError(e.src, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return iter, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *VariableExpression) Exec(_ context.Context, scope *core.Scope) (core.Value, error) {
|
func (e *VariableExpression) Exec(_ context.Context, scope *core.Scope) (core.Value, error) {
|
||||||
return scope.GetVariable(e.name)
|
return scope.GetVariable(e.name)
|
||||||
}
|
}
|
||||||
|
@ -279,6 +279,17 @@ func Unmarshal(value json.RawMessage) (core.Value, error) {
|
|||||||
return Parse(o), nil
|
return Parse(o), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func IsCloneable(value core.Value) Boolean {
|
||||||
|
switch value.Type() {
|
||||||
|
case core.ArrayType:
|
||||||
|
return NewBoolean(true)
|
||||||
|
case core.ObjectType:
|
||||||
|
return NewBoolean(true)
|
||||||
|
default:
|
||||||
|
return NewBoolean(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func ToBoolean(input core.Value) core.Value {
|
func ToBoolean(input core.Value) core.Value {
|
||||||
switch input.Type() {
|
switch input.Type() {
|
||||||
case core.BooleanType:
|
case core.BooleanType:
|
||||||
@ -296,13 +307,54 @@ func ToBoolean(input core.Value) core.Value {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func IsCloneable(value core.Value) Boolean {
|
func ToArray(input core.Value) core.Value {
|
||||||
switch value.Type() {
|
switch input.Type() {
|
||||||
|
case core.BooleanType,
|
||||||
|
core.IntType,
|
||||||
|
core.FloatType,
|
||||||
|
core.StringType,
|
||||||
|
core.DateTimeType:
|
||||||
|
|
||||||
|
return NewArrayWith(input)
|
||||||
|
case core.HTMLElementType,
|
||||||
|
core.HTMLDocumentType:
|
||||||
|
val := input.(HTMLNode)
|
||||||
|
attrs := val.GetAttributes()
|
||||||
|
|
||||||
|
obj, ok := attrs.(*Object)
|
||||||
|
|
||||||
|
if !ok {
|
||||||
|
return NewArray(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
arr := NewArray(int(obj.Length()))
|
||||||
|
|
||||||
|
obj.ForEach(func(value core.Value, key string) bool {
|
||||||
|
arr.Push(value)
|
||||||
|
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
|
||||||
|
return obj
|
||||||
case core.ArrayType:
|
case core.ArrayType:
|
||||||
return NewBoolean(true)
|
return input.Copy()
|
||||||
case core.ObjectType:
|
case core.ObjectType:
|
||||||
return NewBoolean(true)
|
obj, ok := input.(*Object)
|
||||||
|
|
||||||
|
if !ok {
|
||||||
|
return NewArray(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
arr := NewArray(int(obj.Length()))
|
||||||
|
|
||||||
|
obj.ForEach(func(value core.Value, key string) bool {
|
||||||
|
arr.Push(value)
|
||||||
|
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
|
||||||
|
return obj
|
||||||
default:
|
default:
|
||||||
return NewBoolean(false)
|
return NewArray(0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,10 @@
|
|||||||
package arrays
|
package arrays
|
||||||
|
|
||||||
import "github.com/MontFerret/ferret/pkg/runtime/core"
|
import (
|
||||||
|
"github.com/MontFerret/ferret/pkg/runtime/collections"
|
||||||
|
"github.com/MontFerret/ferret/pkg/runtime/core"
|
||||||
|
"github.com/MontFerret/ferret/pkg/runtime/values"
|
||||||
|
)
|
||||||
|
|
||||||
func NewLib() map[string]core.Function {
|
func NewLib() map[string]core.Function {
|
||||||
return map[string]core.Function{
|
return map[string]core.Function{
|
||||||
@ -29,3 +33,19 @@ func NewLib() map[string]core.Function {
|
|||||||
"UNSHIFT": Unshift,
|
"UNSHIFT": Unshift,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func toArray(iterator collections.Iterator) (core.Value, error) {
|
||||||
|
arr := values.NewArray(10)
|
||||||
|
|
||||||
|
for iterator.HasNext() {
|
||||||
|
ds, err := iterator.Next()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return values.None, err
|
||||||
|
}
|
||||||
|
|
||||||
|
arr.Push(ds.Get(collections.DefaultValueVar))
|
||||||
|
}
|
||||||
|
|
||||||
|
return arr, nil
|
||||||
|
}
|
||||||
|
@ -31,8 +31,8 @@ func Sorted(_ context.Context, args ...core.Value) (core.Value, error) {
|
|||||||
return values.NewArray(0), nil
|
return values.NewArray(0), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
sorter, err := collections.NewSorter(func(first core.Value, second core.Value) (int, error) {
|
sorter, err := collections.NewSorter(func(first collections.DataSet, second collections.DataSet) (int, error) {
|
||||||
return first.Compare(second), nil
|
return first.Get(collections.DefaultValueVar).Compare(second.Get(collections.DefaultValueVar)), nil
|
||||||
}, collections.SortDirectionAsc)
|
}, collections.SortDirectionAsc)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -40,7 +40,7 @@ func Sorted(_ context.Context, args ...core.Value) (core.Value, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
iterator, err := collections.NewSortIterator(
|
iterator, err := collections.NewSortIterator(
|
||||||
collections.NewArrayIterator(arr),
|
collections.NewDefaultIndexedIterator(arr),
|
||||||
sorter,
|
sorter,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -48,5 +48,5 @@ func Sorted(_ context.Context, args ...core.Value) (core.Value, error) {
|
|||||||
return values.None, err
|
return values.None, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return collections.ToArray(iterator)
|
return toArray(iterator)
|
||||||
}
|
}
|
||||||
|
@ -32,15 +32,18 @@ func SortedUnique(_ context.Context, args ...core.Value) (core.Value, error) {
|
|||||||
return values.NewArray(0), nil
|
return values.NewArray(0), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
sorter, err := collections.NewSorter(func(first core.Value, second core.Value) (int, error) {
|
sorter, err := collections.NewSorter(func(first collections.DataSet, second collections.DataSet) (int, error) {
|
||||||
return first.Compare(second), nil
|
return first.Get(collections.DefaultValueVar).Compare(second.Get(collections.DefaultValueVar)), nil
|
||||||
}, collections.SortDirectionAsc)
|
}, collections.SortDirectionAsc)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return values.None, err
|
return values.None, err
|
||||||
}
|
}
|
||||||
|
|
||||||
uniqIterator, err := collections.NewUniqueIterator(collections.NewArrayIterator(arr))
|
uniqIterator, err := collections.NewUniqueIterator(
|
||||||
|
collections.NewDefaultIndexedIterator(arr),
|
||||||
|
collections.DefaultValueVar,
|
||||||
|
)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return values.None, err
|
return values.None, err
|
||||||
@ -55,5 +58,5 @@ func SortedUnique(_ context.Context, args ...core.Value) (core.Value, error) {
|
|||||||
return values.None, err
|
return values.None, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return collections.ToArray(iterator)
|
return toArray(iterator)
|
||||||
}
|
}
|
||||||
|
@ -31,12 +31,13 @@ func Unique(_ context.Context, args ...core.Value) (core.Value, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
iterator, err := collections.NewUniqueIterator(
|
iterator, err := collections.NewUniqueIterator(
|
||||||
collections.NewArrayIterator(arr),
|
collections.NewDefaultIndexedIterator(arr),
|
||||||
|
collections.DefaultValueVar,
|
||||||
)
|
)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return values.None, err
|
return values.None, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return collections.ToArray(iterator)
|
return toArray(iterator)
|
||||||
}
|
}
|
||||||
|
@ -2,13 +2,11 @@ package types
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
"github.com/MontFerret/ferret/pkg/runtime/collections"
|
|
||||||
"github.com/MontFerret/ferret/pkg/runtime/core"
|
"github.com/MontFerret/ferret/pkg/runtime/core"
|
||||||
"github.com/MontFerret/ferret/pkg/runtime/values"
|
"github.com/MontFerret/ferret/pkg/runtime/values"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ToArray takes an input value of any type and convert it into an array value.
|
// toArray takes an input value of any type and convert it into an array value.
|
||||||
// @param (Value) - Input value of arbitrary type.
|
// @param (Value) - Input value of arbitrary type.
|
||||||
// @returns (Array)
|
// @returns (Array)
|
||||||
// None is converted to an empty array
|
// None is converted to an empty array
|
||||||
@ -24,30 +22,5 @@ func ToArray(_ context.Context, args ...core.Value) (core.Value, error) {
|
|||||||
|
|
||||||
arg := args[0]
|
arg := args[0]
|
||||||
|
|
||||||
switch arg.Type() {
|
return values.ToArray(arg), nil
|
||||||
case core.BooleanType,
|
|
||||||
core.IntType,
|
|
||||||
core.FloatType,
|
|
||||||
core.StringType,
|
|
||||||
core.DateTimeType:
|
|
||||||
return values.NewArrayWith(arg), nil
|
|
||||||
case core.HTMLElementType,
|
|
||||||
core.HTMLDocumentType:
|
|
||||||
val := arg.(values.HTMLNode)
|
|
||||||
attrs := val.GetAttributes()
|
|
||||||
|
|
||||||
obj, ok := attrs.(*values.Object)
|
|
||||||
|
|
||||||
if !ok {
|
|
||||||
return values.NewArray(0), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return collections.ToArray(collections.NewObjectIterator(obj))
|
|
||||||
case core.ArrayType:
|
|
||||||
return arg, nil
|
|
||||||
case core.ObjectType:
|
|
||||||
return collections.ToArray(collections.NewObjectIterator(arg.(*values.Object)))
|
|
||||||
default:
|
|
||||||
return values.NewArray(0), nil
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user