mirror of
https://github.com/MontFerret/ferret.git
synced 2024-12-14 11:23: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,17 +47,18 @@ func TestParam(t *testing.T) {
|
||||
})
|
||||
|
||||
Convey("Should be possible to use in range", t, func() {
|
||||
out := compiler.New().
|
||||
prog := compiler.New().
|
||||
MustCompile(`
|
||||
FOR i IN @start..@end
|
||||
SORT i
|
||||
RETURN i
|
||||
`).
|
||||
MustRun(
|
||||
context.Background(),
|
||||
runtime.WithParam("start", 1),
|
||||
runtime.WithParam("end", 4),
|
||||
)
|
||||
`)
|
||||
|
||||
out := prog.MustRun(
|
||||
context.Background(),
|
||||
runtime.WithParam("start", 1),
|
||||
runtime.WithParam("end", 4),
|
||||
)
|
||||
|
||||
So(string(out), ShouldEqual, `[1,2,3,4]`)
|
||||
|
||||
|
@ -58,6 +58,18 @@ func (s *scope) SetVariable(name string) error {
|
||||
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 {
|
||||
return newScope(s)
|
||||
}
|
||||
|
@ -16,11 +16,15 @@ import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
type visitor struct {
|
||||
*fql.BaseFqlParserVisitor
|
||||
src string
|
||||
funcs map[string]core.Function
|
||||
}
|
||||
type (
|
||||
forOption func(f *expressions.ForExpression) error
|
||||
|
||||
visitor struct {
|
||||
*fql.BaseFqlParserVisitor
|
||||
src string
|
||||
funcs map[string]core.Function
|
||||
}
|
||||
)
|
||||
|
||||
func newVisitor(src string, funcs map[string]core.Function) *visitor {
|
||||
return &visitor{
|
||||
@ -155,6 +159,7 @@ func (v *visitor) doVisitForExpression(ctx *fql.ForExpressionContext, scope *sco
|
||||
var valVarName string
|
||||
var keyVarName string
|
||||
|
||||
parsedClauses := make([]forOption, 0, 10)
|
||||
valVar := ctx.ForExpressionValueVariable()
|
||||
valVarName = valVar.GetText()
|
||||
forInScope := scope.Fork()
|
||||
@ -167,12 +172,95 @@ func (v *visitor) doVisitForExpression(ctx *fql.ForExpressionContext, scope *sco
|
||||
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 {
|
||||
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()
|
||||
predicate := expressions.NewBlockExpression(len(body) + 1)
|
||||
|
||||
@ -192,7 +280,6 @@ func (v *visitor) doVisitForExpression(ctx *fql.ForExpressionContext, scope *sco
|
||||
|
||||
var spread bool
|
||||
var distinct bool
|
||||
var distinctSrc core.SourceMap
|
||||
forRetCtx := ctx.ForExpressionReturn().(*fql.ForExpressionReturnContext)
|
||||
returnCtx := forRetCtx.ReturnExpression()
|
||||
|
||||
@ -208,8 +295,6 @@ func (v *visitor) doVisitForExpression(ctx *fql.ForExpressionContext, scope *sco
|
||||
|
||||
if distinctCtx != nil {
|
||||
distinct = true
|
||||
token := distinctCtx.GetSymbol()
|
||||
distinctSrc = core.NewSourceMap(token.GetText(), token.GetLine(), token.GetColumn())
|
||||
}
|
||||
|
||||
predicate.Add(returnExp)
|
||||
@ -228,10 +313,9 @@ func (v *visitor) doVisitForExpression(ctx *fql.ForExpressionContext, scope *sco
|
||||
|
||||
forExp, err := expressions.NewForExpression(
|
||||
v.getSourceMap(ctx),
|
||||
valVarName,
|
||||
keyVarName,
|
||||
src,
|
||||
predicate,
|
||||
distinct,
|
||||
spread,
|
||||
)
|
||||
|
||||
@ -239,52 +323,10 @@ func (v *visitor) doVisitForExpression(ctx *fql.ForExpressionContext, scope *sco
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if distinct {
|
||||
forExp.AddDistinct(distinctSrc)
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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...)
|
||||
// add all available clauses
|
||||
for _, clause := range parsedClauses {
|
||||
if err := clause(forExp); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
@ -392,7 +434,190 @@ func (v *visitor) createSort(ctx *fql.SortClauseContext, scope *scope) ([]*claus
|
||||
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()
|
||||
|
||||
if arr != nil {
|
||||
@ -454,7 +679,7 @@ func (v *visitor) doVisitForExpressionBody(ctx *fql.ForExpressionBodyContext, sc
|
||||
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()
|
||||
|
||||
_, err := scope.GetVariable(varName)
|
||||
@ -516,7 +741,7 @@ func (v *visitor) doVisitMemberExpression(ctx *fql.MemberExpressionContext, scop
|
||||
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()
|
||||
props := make([]*literals.ObjectPropertyAssignment, 0, len(assignments))
|
||||
|
||||
@ -553,7 +778,13 @@ func (v *visitor) doVisitObjectLiteral(ctx *fql.ObjectLiteralContext, scope *sco
|
||||
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
|
||||
@ -598,7 +829,7 @@ func (v *visitor) doVisitShorthandPropertyNameContext(ctx *fql.ShorthandProperty
|
||||
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()
|
||||
|
||||
if listCtx == nil {
|
||||
@ -657,7 +888,7 @@ func (v *visitor) doVisitNoneLiteral(_ *fql.NoneLiteralContext) (core.Expression
|
||||
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()
|
||||
|
||||
// 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)
|
||||
|
||||
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)
|
||||
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()
|
||||
|
||||
return expressions.NewParameterExpression(
|
||||
|
@ -75,41 +75,37 @@ sortClauseExpression
|
||||
;
|
||||
|
||||
collectClause
|
||||
: Collect collectVariable Assign expression
|
||||
| Collect collectVariable Assign expression Into collectGroupVariable
|
||||
| Collect collectVariable Assign expression Into collectGroupVariable Keep collectKeepVariable
|
||||
| Collect collectVariable Assign expression With Count collectCountVariable
|
||||
| Collect collectVariable Assign expression Aggregate collectAggregateVariable Assign collectAggregateExpression
|
||||
| Collect Aggregate collectAggregateVariable Assign collectAggregateExpression
|
||||
| Collect With Count Into collectCountVariable
|
||||
: Collect collectCounter
|
||||
| Collect collectAggregator
|
||||
| Collect collectGrouping collectAggregator
|
||||
| Collect collectGrouping collectGroupVariable
|
||||
| Collect collectGrouping collectCounter
|
||||
| Collect collectGrouping
|
||||
;
|
||||
|
||||
collectVariable
|
||||
: Identifier
|
||||
collectSelector
|
||||
: Identifier Assign expression
|
||||
;
|
||||
|
||||
collectGrouping
|
||||
: collectSelector (Comma collectSelector)*
|
||||
;
|
||||
|
||||
collectAggregator
|
||||
: Aggregate collectAggregateSelector (Comma collectAggregateSelector)*
|
||||
;
|
||||
|
||||
collectAggregateSelector
|
||||
: Identifier Assign functionCallExpression
|
||||
;
|
||||
|
||||
collectGroupVariable
|
||||
: Identifier
|
||||
: Into collectSelector
|
||||
| Into Identifier (Keep Identifier)?
|
||||
;
|
||||
|
||||
collectKeepVariable
|
||||
: Identifier
|
||||
;
|
||||
|
||||
collectCountVariable
|
||||
: Identifier
|
||||
;
|
||||
|
||||
collectAggregateVariable
|
||||
: Identifier
|
||||
;
|
||||
|
||||
collectAggregateExpression
|
||||
: expression
|
||||
;
|
||||
|
||||
collectOption
|
||||
:
|
||||
collectCounter
|
||||
: With Count Into Identifier
|
||||
;
|
||||
|
||||
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",
|
||||
"For", "Return", "Distinct", "Filter", "Sort", "Limit", "Let", "Collect",
|
||||
"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",
|
||||
}
|
||||
|
||||
@ -288,7 +288,7 @@ var lexerRuleNames = []string{
|
||||
"And", "Or", "Range", "Assign", "QuestionMark", "RegexNotMatch", "RegexMatch",
|
||||
"For", "Return", "Distinct", "Filter", "Sort", "Limit", "Let", "Collect",
|
||||
"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",
|
||||
"HexDigit", "DecimalIntegerLiteral", "ExponentPart", "Letter", "Symbols",
|
||||
"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.
|
||||
func (s *BaseFqlParserListener) ExitCollectClause(ctx *CollectClauseContext) {}
|
||||
|
||||
// EnterCollectVariable is called when production collectVariable is entered.
|
||||
func (s *BaseFqlParserListener) EnterCollectVariable(ctx *CollectVariableContext) {}
|
||||
// EnterCollectSelector is called when production collectSelector is entered.
|
||||
func (s *BaseFqlParserListener) EnterCollectSelector(ctx *CollectSelectorContext) {}
|
||||
|
||||
// ExitCollectVariable is called when production collectVariable is exited.
|
||||
func (s *BaseFqlParserListener) ExitCollectVariable(ctx *CollectVariableContext) {}
|
||||
// ExitCollectSelector is called when production collectSelector is exited.
|
||||
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.
|
||||
func (s *BaseFqlParserListener) EnterCollectGroupVariable(ctx *CollectGroupVariableContext) {}
|
||||
@ -124,37 +142,11 @@ func (s *BaseFqlParserListener) EnterCollectGroupVariable(ctx *CollectGroupVaria
|
||||
// ExitCollectGroupVariable is called when production collectGroupVariable is exited.
|
||||
func (s *BaseFqlParserListener) ExitCollectGroupVariable(ctx *CollectGroupVariableContext) {}
|
||||
|
||||
// EnterCollectKeepVariable is called when production collectKeepVariable is entered.
|
||||
func (s *BaseFqlParserListener) EnterCollectKeepVariable(ctx *CollectKeepVariableContext) {}
|
||||
// EnterCollectCounter is called when production collectCounter is entered.
|
||||
func (s *BaseFqlParserListener) EnterCollectCounter(ctx *CollectCounterContext) {}
|
||||
|
||||
// ExitCollectKeepVariable is called when production collectKeepVariable is exited.
|
||||
func (s *BaseFqlParserListener) ExitCollectKeepVariable(ctx *CollectKeepVariableContext) {}
|
||||
|
||||
// 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) {}
|
||||
// ExitCollectCounter is called when production collectCounter is exited.
|
||||
func (s *BaseFqlParserListener) ExitCollectCounter(ctx *CollectCounterContext) {}
|
||||
|
||||
// EnterForExpressionBody is called when production forExpressionBody is entered.
|
||||
func (s *BaseFqlParserListener) EnterForExpressionBody(ctx *ForExpressionBodyContext) {}
|
||||
|
@ -67,7 +67,19 @@ func (v *BaseFqlParserVisitor) VisitCollectClause(ctx *CollectClauseContext) int
|
||||
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)
|
||||
}
|
||||
|
||||
@ -75,23 +87,7 @@ func (v *BaseFqlParserVisitor) VisitCollectGroupVariable(ctx *CollectGroupVariab
|
||||
return v.VisitChildren(ctx)
|
||||
}
|
||||
|
||||
func (v *BaseFqlParserVisitor) VisitCollectKeepVariable(ctx *CollectKeepVariableContext) 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{} {
|
||||
func (v *BaseFqlParserVisitor) VisitCollectCounter(ctx *CollectCounterContext) interface{} {
|
||||
return v.VisitChildren(ctx)
|
||||
}
|
||||
|
||||
|
@ -52,26 +52,23 @@ type FqlParserListener interface {
|
||||
// EnterCollectClause is called when entering the collectClause production.
|
||||
EnterCollectClause(c *CollectClauseContext)
|
||||
|
||||
// EnterCollectVariable is called when entering the collectVariable production.
|
||||
EnterCollectVariable(c *CollectVariableContext)
|
||||
// EnterCollectSelector is called when entering the collectSelector production.
|
||||
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(c *CollectGroupVariableContext)
|
||||
|
||||
// EnterCollectKeepVariable is called when entering the collectKeepVariable production.
|
||||
EnterCollectKeepVariable(c *CollectKeepVariableContext)
|
||||
|
||||
// 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)
|
||||
// EnterCollectCounter is called when entering the collectCounter production.
|
||||
EnterCollectCounter(c *CollectCounterContext)
|
||||
|
||||
// EnterForExpressionBody is called when entering the forExpressionBody production.
|
||||
EnterForExpressionBody(c *ForExpressionBodyContext)
|
||||
@ -208,26 +205,23 @@ type FqlParserListener interface {
|
||||
// ExitCollectClause is called when exiting the collectClause production.
|
||||
ExitCollectClause(c *CollectClauseContext)
|
||||
|
||||
// ExitCollectVariable is called when exiting the collectVariable production.
|
||||
ExitCollectVariable(c *CollectVariableContext)
|
||||
// ExitCollectSelector is called when exiting the collectSelector production.
|
||||
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(c *CollectGroupVariableContext)
|
||||
|
||||
// ExitCollectKeepVariable is called when exiting the collectKeepVariable production.
|
||||
ExitCollectKeepVariable(c *CollectKeepVariableContext)
|
||||
|
||||
// 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)
|
||||
// ExitCollectCounter is called when exiting the collectCounter production.
|
||||
ExitCollectCounter(c *CollectCounterContext)
|
||||
|
||||
// ExitForExpressionBody is called when exiting the forExpressionBody production.
|
||||
ExitForExpressionBody(c *ForExpressionBodyContext)
|
||||
|
@ -52,26 +52,23 @@ type FqlParserVisitor interface {
|
||||
// Visit a parse tree produced by FqlParser#collectClause.
|
||||
VisitCollectClause(ctx *CollectClauseContext) interface{}
|
||||
|
||||
// Visit a parse tree produced by FqlParser#collectVariable.
|
||||
VisitCollectVariable(ctx *CollectVariableContext) interface{}
|
||||
// Visit a parse tree produced by FqlParser#collectSelector.
|
||||
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.
|
||||
VisitCollectGroupVariable(ctx *CollectGroupVariableContext) interface{}
|
||||
|
||||
// Visit a parse tree produced by FqlParser#collectKeepVariable.
|
||||
VisitCollectKeepVariable(ctx *CollectKeepVariableContext) 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#collectCounter.
|
||||
VisitCollectCounter(ctx *CollectCounterContext) interface{}
|
||||
|
||||
// Visit a parse tree produced by FqlParser#forExpressionBody.
|
||||
VisitForExpressionBody(ctx *ForExpressionBodyContext) interface{}
|
||||
|
@ -1,7 +1,25 @@
|
||||
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 {
|
||||
Length() values.Int
|
||||
}
|
||||
type (
|
||||
Collection interface {
|
||||
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
|
||||
}
|
@ -5,5 +5,14 @@ import (
|
||||
)
|
||||
|
||||
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 (
|
||||
"github.com/MontFerret/ferret/pkg/runtime/core"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/values"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type (
|
||||
FilterPredicate func(val core.Value, key core.Value) (bool, error)
|
||||
FilterIterator struct {
|
||||
FilterPredicate func(set DataSet) (bool, error)
|
||||
|
||||
FilterIterator struct {
|
||||
src Iterator
|
||||
predicate FilterPredicate
|
||||
value core.Value
|
||||
key core.Value
|
||||
dataSet DataSet
|
||||
ready bool
|
||||
}
|
||||
)
|
||||
@ -35,34 +34,33 @@ func (iterator *FilterIterator) HasNext() bool {
|
||||
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 {
|
||||
val := iterator.value
|
||||
key := iterator.key
|
||||
ds := iterator.dataSet
|
||||
|
||||
iterator.filter()
|
||||
|
||||
return val, key, nil
|
||||
return ds, nil
|
||||
}
|
||||
|
||||
return values.None, values.None, ErrExhausted
|
||||
return nil, ErrExhausted
|
||||
}
|
||||
|
||||
func (iterator *FilterIterator) filter() {
|
||||
var doNext bool
|
||||
|
||||
for iterator.src.HasNext() {
|
||||
val, key, err := iterator.src.Next()
|
||||
set, err := iterator.src.Next()
|
||||
|
||||
if err != nil {
|
||||
doNext = false
|
||||
break
|
||||
}
|
||||
|
||||
take, err := iterator.predicate(val, key)
|
||||
take, err := iterator.predicate(set)
|
||||
|
||||
if err != nil {
|
||||
doNext = false
|
||||
@ -71,14 +69,12 @@ func (iterator *FilterIterator) filter() {
|
||||
|
||||
if take == true {
|
||||
doNext = true
|
||||
iterator.value = val
|
||||
iterator.key = key
|
||||
iterator.dataSet = set
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if doNext == false {
|
||||
iterator.value = nil
|
||||
iterator.key = nil
|
||||
iterator.dataSet = nil
|
||||
}
|
||||
}
|
||||
|
@ -20,15 +20,15 @@ func TestFilter(t *testing.T) {
|
||||
values.NewInt(5),
|
||||
}
|
||||
|
||||
predicate := func(val core.Value, _ core.Value) (bool, error) {
|
||||
i := float64(val.Unwrap().(int))
|
||||
predicate := func(ds collections.DataSet) (bool, error) {
|
||||
i := float64(ds.Get(collections.DefaultValueVar).Unwrap().(int))
|
||||
calc := float64(i / 2)
|
||||
|
||||
return calc == math.Floor(calc), nil
|
||||
}
|
||||
|
||||
iter, err := collections.NewFilterIterator(
|
||||
collections.NewSliceIterator(arr),
|
||||
sliceIterator(arr),
|
||||
predicate,
|
||||
)
|
||||
|
||||
@ -37,7 +37,7 @@ func TestFilter(t *testing.T) {
|
||||
res := make([]core.Value, 0, len(arr))
|
||||
|
||||
for iter.HasNext() {
|
||||
item, _, err := iter.Next()
|
||||
item, _, err := next(iter)
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
@ -47,7 +47,7 @@ func TestFilter(t *testing.T) {
|
||||
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{
|
||||
values.NewInt(1),
|
||||
values.NewInt(2),
|
||||
@ -56,8 +56,8 @@ func TestFilter(t *testing.T) {
|
||||
values.NewInt(5),
|
||||
}
|
||||
|
||||
predicate := func(_ core.Value, key core.Value) (bool, error) {
|
||||
i := float64(key.Unwrap().(int))
|
||||
predicate := func(ds collections.DataSet) (bool, error) {
|
||||
i := float64(ds.Get(collections.DefaultKeyVar).Unwrap().(int))
|
||||
|
||||
if i == 0 {
|
||||
return false, nil
|
||||
@ -69,7 +69,7 @@ func TestFilter(t *testing.T) {
|
||||
}
|
||||
|
||||
iter, err := collections.NewFilterIterator(
|
||||
collections.NewSliceIterator(arr),
|
||||
sliceIterator(arr),
|
||||
predicate,
|
||||
)
|
||||
|
||||
@ -78,7 +78,7 @@ func TestFilter(t *testing.T) {
|
||||
res := make([]core.Value, 0, len(arr))
|
||||
|
||||
for iter.HasNext() {
|
||||
item, _, err := iter.Next()
|
||||
item, _, err := next(iter)
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
@ -97,12 +97,12 @@ func TestFilter(t *testing.T) {
|
||||
values.NewInt(5),
|
||||
}
|
||||
|
||||
predicate := func(val core.Value, _ core.Value) (bool, error) {
|
||||
predicate := func(_ collections.DataSet) (bool, error) {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
iter, err := collections.NewFilterIterator(
|
||||
collections.NewSliceIterator(arr),
|
||||
sliceIterator(arr),
|
||||
predicate,
|
||||
)
|
||||
|
||||
@ -111,7 +111,7 @@ func TestFilter(t *testing.T) {
|
||||
res := make([]core.Value, 0, len(arr))
|
||||
|
||||
for iter.HasNext() {
|
||||
item, _, err := iter.Next()
|
||||
item, _, err := next(iter)
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
@ -130,12 +130,12 @@ func TestFilter(t *testing.T) {
|
||||
values.NewInt(5),
|
||||
}
|
||||
|
||||
predicate := func(val core.Value, _ core.Value) (bool, error) {
|
||||
predicate := func(_ collections.DataSet) (bool, error) {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
iter, err := collections.NewFilterIterator(
|
||||
collections.NewSliceIterator(arr),
|
||||
sliceIterator(arr),
|
||||
predicate,
|
||||
)
|
||||
|
||||
@ -144,7 +144,7 @@ func TestFilter(t *testing.T) {
|
||||
res := make([]core.Value, 0, len(arr))
|
||||
|
||||
for iter.HasNext() {
|
||||
item, _, err := iter.Next()
|
||||
item, _, err := next(iter)
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
@ -163,12 +163,12 @@ func TestFilter(t *testing.T) {
|
||||
values.NewInt(5),
|
||||
}
|
||||
|
||||
predicate := func(val core.Value, _ core.Value) (bool, error) {
|
||||
predicate := func(_ collections.DataSet) (bool, error) {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
iter, err := collections.NewFilterIterator(
|
||||
collections.NewSliceIterator(arr),
|
||||
sliceIterator(arr),
|
||||
predicate,
|
||||
)
|
||||
|
||||
@ -177,15 +177,16 @@ func TestFilter(t *testing.T) {
|
||||
res := make([]core.Value, 0, len(arr))
|
||||
|
||||
for iter.HasNext() {
|
||||
item, _, err := iter.Next()
|
||||
item, _, err := next(iter)
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
res = append(res, item)
|
||||
}
|
||||
|
||||
_, _, err = iter.Next()
|
||||
item, _, err := next(iter)
|
||||
|
||||
So(item, ShouldBeNil)
|
||||
So(err, ShouldBeError)
|
||||
})
|
||||
|
||||
@ -199,17 +200,17 @@ func TestFilter(t *testing.T) {
|
||||
}
|
||||
|
||||
// i < 5
|
||||
predicate1 := func(val core.Value, _ core.Value) (bool, error) {
|
||||
return val.Compare(values.NewInt(5)) == -1, nil
|
||||
predicate1 := func(ds collections.DataSet) (bool, error) {
|
||||
return ds.Get(collections.DefaultValueVar).Compare(values.NewInt(5)) == -1, nil
|
||||
}
|
||||
|
||||
// i > 2
|
||||
predicate2 := func(val core.Value, _ core.Value) (bool, error) {
|
||||
return val.Compare(values.NewInt(2)) == 1, nil
|
||||
predicate2 := func(ds collections.DataSet) (bool, error) {
|
||||
return ds.Get(collections.DefaultValueVar).Compare(values.NewInt(2)) == 1, nil
|
||||
}
|
||||
|
||||
it, _ := collections.NewFilterIterator(
|
||||
collections.NewSliceIterator(arr),
|
||||
sliceIterator(arr),
|
||||
predicate1,
|
||||
)
|
||||
|
||||
@ -220,10 +221,12 @@ func TestFilter(t *testing.T) {
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
res, err := collections.ToSlice(iter)
|
||||
sets, err := collections.ToSlice(iter)
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
res := toArrayOfValues(sets)
|
||||
|
||||
js, _ := json.Marshal(res)
|
||||
|
||||
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 (
|
||||
"context"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/core"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/values"
|
||||
)
|
||||
|
||||
type (
|
||||
Variables []string
|
||||
|
||||
Iterator interface {
|
||||
HasNext() bool
|
||||
Next() (value core.Value, key core.Value, err error)
|
||||
Next() (DataSet, error)
|
||||
}
|
||||
|
||||
Iterable interface {
|
||||
Iterate() Iterator
|
||||
}
|
||||
|
||||
IterableExpression interface {
|
||||
core.Expression
|
||||
Variables() Variables
|
||||
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) {
|
||||
switch value.Type() {
|
||||
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)
|
||||
func ToSlice(iterator Iterator) ([]DataSet, error) {
|
||||
res := make([]DataSet, 0, 10)
|
||||
|
||||
for iterator.HasNext() {
|
||||
item, _, err := iterator.Next()
|
||||
ds, err := iterator.Next()
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
res = append(res, item)
|
||||
res = append(res, ds)
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
func (i *LimitIterator) Next() (core.Value, core.Value, error) {
|
||||
func (i *LimitIterator) Next() (DataSet, error) {
|
||||
if i.counter() <= i.count {
|
||||
i.currCount++
|
||||
|
||||
return i.src.Next()
|
||||
}
|
||||
|
||||
return nil, nil, ErrExhausted
|
||||
return nil, ErrExhausted
|
||||
}
|
||||
|
||||
func (i *LimitIterator) counter() int {
|
||||
|
@ -19,7 +19,7 @@ func TestLimit(t *testing.T) {
|
||||
}
|
||||
|
||||
src, err := collections.NewLimitIterator(
|
||||
collections.NewSliceIterator(arr),
|
||||
sliceIterator(arr),
|
||||
1,
|
||||
0,
|
||||
)
|
||||
@ -29,7 +29,7 @@ func TestLimit(t *testing.T) {
|
||||
res := make([]core.Value, 0, len(arr))
|
||||
|
||||
for src.HasNext() {
|
||||
item, _, err := src.Next()
|
||||
item, _, err := next(src)
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
@ -49,7 +49,7 @@ func TestLimit(t *testing.T) {
|
||||
}
|
||||
|
||||
src, err := collections.NewLimitIterator(
|
||||
collections.NewSliceIterator(arr),
|
||||
sliceIterator(arr),
|
||||
2,
|
||||
0,
|
||||
)
|
||||
@ -59,7 +59,7 @@ func TestLimit(t *testing.T) {
|
||||
res := make([]core.Value, 0, len(arr))
|
||||
|
||||
for src.HasNext() {
|
||||
item, _, err := src.Next()
|
||||
item, _, err := next(src)
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
@ -80,7 +80,7 @@ func TestLimit(t *testing.T) {
|
||||
|
||||
offset := 2
|
||||
src, err := collections.NewLimitIterator(
|
||||
collections.NewSliceIterator(arr),
|
||||
sliceIterator(arr),
|
||||
2,
|
||||
offset,
|
||||
)
|
||||
@ -90,7 +90,7 @@ func TestLimit(t *testing.T) {
|
||||
res := make([]core.Value, 0, len(arr))
|
||||
|
||||
for src.HasNext() {
|
||||
item, _, err := src.Next()
|
||||
item, _, err := next(src)
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
@ -118,7 +118,7 @@ func TestLimit(t *testing.T) {
|
||||
offset := 3
|
||||
|
||||
src, err := collections.NewLimitIterator(
|
||||
collections.NewSliceIterator(arr),
|
||||
sliceIterator(arr),
|
||||
2,
|
||||
offset,
|
||||
)
|
||||
@ -128,7 +128,7 @@ func TestLimit(t *testing.T) {
|
||||
res := make([]core.Value, 0, len(arr))
|
||||
|
||||
for src.HasNext() {
|
||||
item, _, err := src.Next()
|
||||
item, _, err := next(src)
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
@ -156,7 +156,7 @@ func TestLimit(t *testing.T) {
|
||||
offset := 4
|
||||
|
||||
src, err := collections.NewLimitIterator(
|
||||
collections.NewSliceIterator(arr),
|
||||
sliceIterator(arr),
|
||||
2,
|
||||
offset,
|
||||
)
|
||||
@ -166,7 +166,7 @@ func TestLimit(t *testing.T) {
|
||||
res := make([]core.Value, 0, len(arr))
|
||||
|
||||
for src.HasNext() {
|
||||
item, _, err := src.Next()
|
||||
item, _, err := next(src)
|
||||
|
||||
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 (
|
||||
SortDirection int
|
||||
|
||||
Comparator func(first core.Value, second core.Value) (int, error)
|
||||
Comparator func(first DataSet, second DataSet) (int, error)
|
||||
|
||||
Sorter struct {
|
||||
fn Comparator
|
||||
@ -21,7 +21,9 @@ type (
|
||||
src Iterator
|
||||
sorters []*Sorter
|
||||
ready bool
|
||||
values *SliceIterator
|
||||
values []DataSet
|
||||
err error
|
||||
pos int
|
||||
}
|
||||
)
|
||||
|
||||
@ -71,33 +73,53 @@ func NewSortIterator(
|
||||
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 {
|
||||
// we need to initialize the iterator
|
||||
if iterator.ready == false {
|
||||
iterator.ready = true
|
||||
values, err := iterator.sort()
|
||||
sorted, err := iterator.sort()
|
||||
|
||||
if err != nil {
|
||||
// set to true because we do not want to initialize next time anymore
|
||||
iterator.values = NewSliceIterator(make([]core.Value, 0, 0))
|
||||
// dataSet to true because we do not want to initialize next time anymore
|
||||
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) {
|
||||
return iterator.values.Next()
|
||||
func (iterator *SortIterator) Next() (DataSet, error) {
|
||||
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)
|
||||
|
||||
if err != nil {
|
||||
@ -147,5 +169,5 @@ func (iterator *SortIterator) sort() (*SliceIterator, error) {
|
||||
return nil, failure
|
||||
}
|
||||
|
||||
return NewSliceIterator(res), nil
|
||||
return res, nil
|
||||
}
|
||||
|
@ -9,6 +9,20 @@ import (
|
||||
"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) {
|
||||
Convey("Should sort asc", t, func() {
|
||||
arr := []core.Value{
|
||||
@ -20,14 +34,14 @@ func TestSort(t *testing.T) {
|
||||
}
|
||||
|
||||
s, _ := collections.NewSorter(
|
||||
func(first core.Value, second core.Value) (int, error) {
|
||||
return first.Compare(second), nil
|
||||
func(first collections.DataSet, second collections.DataSet) (int, error) {
|
||||
return first.Get(collections.DefaultValueVar).Compare(second.Get(collections.DefaultValueVar)), nil
|
||||
},
|
||||
collections.SortDirectionAsc,
|
||||
)
|
||||
|
||||
src, err := collections.NewSortIterator(
|
||||
collections.NewSliceIterator(arr),
|
||||
sliceIterator(arr),
|
||||
s,
|
||||
)
|
||||
|
||||
@ -40,7 +54,7 @@ func TestSort(t *testing.T) {
|
||||
numbers := []int{1, 2, 3, 4, 5}
|
||||
|
||||
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(
|
||||
func(first core.Value, second core.Value) (int, error) {
|
||||
return first.Compare(second), nil
|
||||
func(first collections.DataSet, second collections.DataSet) (int, error) {
|
||||
return first.Get(collections.DefaultValueVar).Compare(second.Get(collections.DefaultValueVar)), nil
|
||||
},
|
||||
collections.SortDirectionDesc,
|
||||
)
|
||||
|
||||
src, err := collections.NewSortIterator(
|
||||
collections.NewSliceIterator(arr),
|
||||
sliceIterator(arr),
|
||||
s,
|
||||
)
|
||||
|
||||
@ -74,7 +88,7 @@ func TestSort(t *testing.T) {
|
||||
numbers := []int{5, 4, 3, 2, 1}
|
||||
|
||||
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(
|
||||
func(first core.Value, second core.Value) (int, error) {
|
||||
o1, _ := first.(*values.Object).Get("one")
|
||||
o2, _ := second.(*values.Object).Get("one")
|
||||
func(first collections.DataSet, second collections.DataSet) (int, error) {
|
||||
o1, _ := first.Get(collections.DefaultValueVar).(*values.Object).Get("one")
|
||||
o2, _ := second.Get(collections.DefaultValueVar).(*values.Object).Get("one")
|
||||
|
||||
return o1.Compare(o2), nil
|
||||
},
|
||||
@ -110,9 +124,9 @@ func TestSort(t *testing.T) {
|
||||
)
|
||||
|
||||
s2, _ := collections.NewSorter(
|
||||
func(first core.Value, second core.Value) (int, error) {
|
||||
o1, _ := first.(*values.Object).Get("two")
|
||||
o2, _ := second.(*values.Object).Get("two")
|
||||
func(first collections.DataSet, second collections.DataSet) (int, error) {
|
||||
o1, _ := first.Get(collections.DefaultValueVar).(*values.Object).Get("two")
|
||||
o2, _ := second.Get(collections.DefaultValueVar).(*values.Object).Get("two")
|
||||
|
||||
return o1.Compare(o2), nil
|
||||
},
|
||||
@ -120,17 +134,19 @@ func TestSort(t *testing.T) {
|
||||
)
|
||||
|
||||
src, err := collections.NewSortIterator(
|
||||
collections.NewSliceIterator(arr),
|
||||
sliceIterator(arr),
|
||||
s1,
|
||||
s2,
|
||||
)
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
res, err := collections.ToSlice(src)
|
||||
sets, err := collections.ToSlice(src)
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
res := toValues(sets)
|
||||
|
||||
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}]`)
|
||||
@ -158,9 +174,9 @@ func TestSort(t *testing.T) {
|
||||
}
|
||||
|
||||
s1, _ := collections.NewSorter(
|
||||
func(first core.Value, second core.Value) (int, error) {
|
||||
o1, _ := first.(*values.Object).Get("one")
|
||||
o2, _ := second.(*values.Object).Get("one")
|
||||
func(first collections.DataSet, second collections.DataSet) (int, error) {
|
||||
o1, _ := first.Get(collections.DefaultValueVar).(*values.Object).Get("one")
|
||||
o2, _ := second.Get(collections.DefaultValueVar).(*values.Object).Get("one")
|
||||
|
||||
return o1.Compare(o2), nil
|
||||
},
|
||||
@ -168,9 +184,9 @@ func TestSort(t *testing.T) {
|
||||
)
|
||||
|
||||
s2, _ := collections.NewSorter(
|
||||
func(first core.Value, second core.Value) (int, error) {
|
||||
o1, _ := first.(*values.Object).Get("two")
|
||||
o2, _ := second.(*values.Object).Get("two")
|
||||
func(first collections.DataSet, second collections.DataSet) (int, error) {
|
||||
o1, _ := first.Get(collections.DefaultValueVar).(*values.Object).Get("two")
|
||||
o2, _ := second.Get(collections.DefaultValueVar).(*values.Object).Get("two")
|
||||
|
||||
return o1.Compare(o2), nil
|
||||
},
|
||||
@ -178,17 +194,19 @@ func TestSort(t *testing.T) {
|
||||
)
|
||||
|
||||
src, err := collections.NewSortIterator(
|
||||
collections.NewSliceIterator(arr),
|
||||
sliceIterator(arr),
|
||||
s1,
|
||||
s2,
|
||||
)
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
res, err := collections.ToSlice(src)
|
||||
sets, err := collections.ToSlice(src)
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
res := toValues(sets)
|
||||
|
||||
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}]`)
|
||||
@ -216,9 +234,9 @@ func TestSort(t *testing.T) {
|
||||
}
|
||||
|
||||
s1, _ := collections.NewSorter(
|
||||
func(first core.Value, second core.Value) (int, error) {
|
||||
o1, _ := first.(*values.Object).Get("one")
|
||||
o2, _ := second.(*values.Object).Get("one")
|
||||
func(first collections.DataSet, second collections.DataSet) (int, error) {
|
||||
o1, _ := first.Get(collections.DefaultValueVar).(*values.Object).Get("one")
|
||||
o2, _ := second.Get(collections.DefaultValueVar).(*values.Object).Get("one")
|
||||
|
||||
return o1.Compare(o2), nil
|
||||
},
|
||||
@ -226,9 +244,9 @@ func TestSort(t *testing.T) {
|
||||
)
|
||||
|
||||
s2, _ := collections.NewSorter(
|
||||
func(first core.Value, second core.Value) (int, error) {
|
||||
o1, _ := first.(*values.Object).Get("two")
|
||||
o2, _ := second.(*values.Object).Get("two")
|
||||
func(first collections.DataSet, second collections.DataSet) (int, error) {
|
||||
o1, _ := first.Get(collections.DefaultValueVar).(*values.Object).Get("two")
|
||||
o2, _ := second.Get(collections.DefaultValueVar).(*values.Object).Get("two")
|
||||
|
||||
return o1.Compare(o2), nil
|
||||
},
|
||||
@ -236,17 +254,19 @@ func TestSort(t *testing.T) {
|
||||
)
|
||||
|
||||
src, err := collections.NewSortIterator(
|
||||
collections.NewSliceIterator(arr),
|
||||
sliceIterator(arr),
|
||||
s1,
|
||||
s2,
|
||||
)
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
res, err := collections.ToSlice(src)
|
||||
sets, err := collections.ToSlice(src)
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
res := toValues(sets)
|
||||
|
||||
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}]`)
|
||||
@ -274,9 +294,9 @@ func TestSort(t *testing.T) {
|
||||
}
|
||||
|
||||
s1, _ := collections.NewSorter(
|
||||
func(first core.Value, second core.Value) (int, error) {
|
||||
o1, _ := first.(*values.Object).Get("one")
|
||||
o2, _ := second.(*values.Object).Get("one")
|
||||
func(first collections.DataSet, second collections.DataSet) (int, error) {
|
||||
o1, _ := first.Get(collections.DefaultValueVar).(*values.Object).Get("one")
|
||||
o2, _ := second.Get(collections.DefaultValueVar).(*values.Object).Get("one")
|
||||
|
||||
return o1.Compare(o2), nil
|
||||
},
|
||||
@ -284,9 +304,9 @@ func TestSort(t *testing.T) {
|
||||
)
|
||||
|
||||
s2, _ := collections.NewSorter(
|
||||
func(first core.Value, second core.Value) (int, error) {
|
||||
o1, _ := first.(*values.Object).Get("two")
|
||||
o2, _ := second.(*values.Object).Get("two")
|
||||
func(first collections.DataSet, second collections.DataSet) (int, error) {
|
||||
o1, _ := first.Get(collections.DefaultValueVar).(*values.Object).Get("two")
|
||||
o2, _ := second.Get(collections.DefaultValueVar).(*values.Object).Get("two")
|
||||
|
||||
return o1.Compare(o2), nil
|
||||
},
|
||||
@ -294,17 +314,19 @@ func TestSort(t *testing.T) {
|
||||
)
|
||||
|
||||
src, err := collections.NewSortIterator(
|
||||
collections.NewSliceIterator(arr),
|
||||
sliceIterator(arr),
|
||||
s1,
|
||||
s2,
|
||||
)
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
res, err := collections.ToSlice(src)
|
||||
sets, err := collections.ToSlice(src)
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
res := toValues(sets)
|
||||
|
||||
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}]`)
|
||||
|
@ -6,22 +6,23 @@ import (
|
||||
|
||||
type (
|
||||
UniqueIterator struct {
|
||||
src Iterator
|
||||
hashes map[uint64]bool
|
||||
value core.Value
|
||||
key core.Value
|
||||
err error
|
||||
src Iterator
|
||||
hashes map[uint64]bool
|
||||
hashKey string
|
||||
dataSet DataSet
|
||||
err error
|
||||
}
|
||||
)
|
||||
|
||||
func NewUniqueIterator(src Iterator) (*UniqueIterator, error) {
|
||||
func NewUniqueIterator(src Iterator, hashKey string) (*UniqueIterator, error) {
|
||||
if src == nil {
|
||||
return nil, core.Error(core.ErrMissedArgument, "source")
|
||||
}
|
||||
|
||||
return &UniqueIterator{
|
||||
src: src,
|
||||
hashes: make(map[uint64]bool),
|
||||
src: src,
|
||||
hashes: make(map[uint64]bool),
|
||||
hashKey: hashKey,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@ -36,26 +37,25 @@ func (iterator *UniqueIterator) HasNext() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
if !core.IsNil(iterator.value) {
|
||||
if iterator.dataSet != nil {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (iterator *UniqueIterator) Next() (core.Value, core.Value, error) {
|
||||
return iterator.value, iterator.key, iterator.err
|
||||
func (iterator *UniqueIterator) Next() (DataSet, error) {
|
||||
return iterator.dataSet, iterator.err
|
||||
}
|
||||
|
||||
func (iterator *UniqueIterator) doNext() {
|
||||
// reset state
|
||||
iterator.err = nil
|
||||
iterator.value = nil
|
||||
iterator.key = nil
|
||||
iterator.dataSet = nil
|
||||
|
||||
// iterate over source until we find a non-unique item
|
||||
for iterator.src.HasNext() {
|
||||
val, key, err := iterator.src.Next()
|
||||
ds, err := iterator.src.Next()
|
||||
|
||||
if err != nil {
|
||||
iterator.err = err
|
||||
@ -63,7 +63,7 @@ func (iterator *UniqueIterator) doNext() {
|
||||
return
|
||||
}
|
||||
|
||||
h := val.Hash()
|
||||
h := ds.Get(iterator.hashKey).Hash()
|
||||
|
||||
_, exists := iterator.hashes[h]
|
||||
|
||||
@ -72,8 +72,7 @@ func (iterator *UniqueIterator) doNext() {
|
||||
}
|
||||
|
||||
iterator.hashes[h] = true
|
||||
iterator.key = key
|
||||
iterator.value = val
|
||||
iterator.dataSet = ds
|
||||
|
||||
return
|
||||
}
|
||||
|
@ -24,15 +24,18 @@ func TestUniqueIterator(t *testing.T) {
|
||||
}
|
||||
|
||||
iter, err := collections.NewUniqueIterator(
|
||||
collections.NewSliceIterator(arr),
|
||||
sliceIterator(arr),
|
||||
collections.DefaultValueVar,
|
||||
)
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
res, err := collections.ToArray(iter)
|
||||
sets, err := collections.ToSlice(iter)
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
res := toArrayOfValues(sets)
|
||||
|
||||
So(res.String(), ShouldEqual, `[1,2,3,4,5,6]`)
|
||||
})
|
||||
|
||||
@ -47,15 +50,18 @@ func TestUniqueIterator(t *testing.T) {
|
||||
}
|
||||
|
||||
iter, err := collections.NewUniqueIterator(
|
||||
collections.NewSliceIterator(arr),
|
||||
sliceIterator(arr),
|
||||
collections.DefaultValueVar,
|
||||
)
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
res, err := collections.ToArray(iter)
|
||||
sets, err := collections.ToSlice(iter)
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
res := toArrayOfValues(sets)
|
||||
|
||||
So(res.String(), ShouldEqual, `[1]`)
|
||||
})
|
||||
|
||||
@ -75,15 +81,18 @@ func TestUniqueIterator(t *testing.T) {
|
||||
}
|
||||
|
||||
iter, err := collections.NewUniqueIterator(
|
||||
collections.NewSliceIterator(arr),
|
||||
sliceIterator(arr),
|
||||
collections.DefaultValueVar,
|
||||
)
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
res, err := collections.ToArray(iter)
|
||||
sets, err := collections.ToSlice(iter)
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
res := toArrayOfValues(sets)
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
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 {
|
||||
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 {
|
||||
*baseClause
|
||||
valVar string
|
||||
keyVar string
|
||||
predicate core.Expression
|
||||
src core.SourceMap
|
||||
dataSource collections.Iterable
|
||||
predicate core.Expression
|
||||
}
|
||||
|
||||
func NewFilterClause(
|
||||
src core.SourceMap,
|
||||
dataSource collections.IterableExpression,
|
||||
valVar string,
|
||||
keyVar string,
|
||||
dataSource collections.Iterable,
|
||||
predicate core.Expression,
|
||||
) *FilterClause {
|
||||
return &FilterClause{
|
||||
&baseClause{src, dataSource},
|
||||
valVar,
|
||||
keyVar,
|
||||
predicate,
|
||||
) (collections.Iterable, error) {
|
||||
if dataSource == nil {
|
||||
return nil, core.Error(core.ErrMissedArgument, "dataSource source")
|
||||
}
|
||||
|
||||
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) {
|
||||
@ -36,13 +43,15 @@ func (clause *FilterClause) Iterate(ctx context.Context, scope *core.Scope) (col
|
||||
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.SetVariable(clause.valVar, val)
|
||||
err := set.Apply(innerScope, variables)
|
||||
|
||||
if clause.keyVar != "" {
|
||||
innerScope.SetVariable(clause.keyVar, key)
|
||||
if err != nil {
|
||||
return false, core.SourceError(clause.src, err)
|
||||
}
|
||||
|
||||
ret, err := clause.predicate.Exec(ctx, innerScope)
|
||||
|
@ -7,26 +7,45 @@ import (
|
||||
)
|
||||
|
||||
type LimitClause struct {
|
||||
*baseClause
|
||||
count int
|
||||
offset int
|
||||
src core.SourceMap
|
||||
dataSource collections.Iterable
|
||||
count int
|
||||
offset int
|
||||
}
|
||||
|
||||
func NewLimitClause(
|
||||
src core.SourceMap,
|
||||
dataSource collections.IterableExpression,
|
||||
dataSource collections.Iterable,
|
||||
count int,
|
||||
offset int,
|
||||
) *LimitClause {
|
||||
return &LimitClause{&baseClause{src, dataSource}, count, offset}
|
||||
) (collections.Iterable, error) {
|
||||
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) {
|
||||
src, err := clause.dataSource.Iterate(ctx, scope)
|
||||
|
||||
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
|
||||
}
|
||||
SortClause struct {
|
||||
*baseClause
|
||||
variableName string
|
||||
sorters []*SorterExpression
|
||||
src core.SourceMap
|
||||
dataSource collections.Iterable
|
||||
sorters []*SorterExpression
|
||||
}
|
||||
)
|
||||
|
||||
func NewSorterExpression(expression core.Expression, direction collections.SortDirection) (*SorterExpression, error) {
|
||||
if expression == nil {
|
||||
return nil, core.Error(core.ErrMissedArgument, "expression")
|
||||
return nil, core.Error(core.ErrMissedArgument, "reducer")
|
||||
}
|
||||
|
||||
if !collections.IsValidSortDirection(direction) {
|
||||
@ -32,11 +32,22 @@ func NewSorterExpression(expression core.Expression, direction collections.SortD
|
||||
|
||||
func NewSortClause(
|
||||
src core.SourceMap,
|
||||
dataSource collections.IterableExpression,
|
||||
variableName string,
|
||||
dataSource collections.Iterable,
|
||||
sorters ...*SorterExpression,
|
||||
) *SortClause {
|
||||
return &SortClause{&baseClause{src, dataSource}, variableName, sorters}
|
||||
) (collections.Iterable, error) {
|
||||
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) {
|
||||
@ -47,12 +58,13 @@ func (clause *SortClause) Iterate(ctx context.Context, scope *core.Scope) (colle
|
||||
}
|
||||
|
||||
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 {
|
||||
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.SetVariable(clause.variableName, first)
|
||||
first.Apply(scope1, variables)
|
||||
|
||||
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.SetVariable(clause.variableName, second)
|
||||
second.Apply(scope2, variables)
|
||||
|
||||
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 {
|
||||
src core.SourceMap
|
||||
valVar string
|
||||
keyVar string
|
||||
dataSource collections.IterableExpression
|
||||
dataSource collections.Iterable
|
||||
predicate core.Expression
|
||||
distinct bool
|
||||
spread bool
|
||||
}
|
||||
|
||||
func NewForExpression(
|
||||
src core.SourceMap,
|
||||
valVar string,
|
||||
keyVar string,
|
||||
dataSource collections.IterableExpression,
|
||||
dataSource collections.Iterable,
|
||||
predicate core.Expression,
|
||||
distinct,
|
||||
spread bool,
|
||||
) (*ForExpression, error) {
|
||||
if valVar == "" {
|
||||
return nil, errors.Wrap(core.ErrInvalidArgument, "valVar is empty")
|
||||
}
|
||||
|
||||
if core.IsNil(dataSource) {
|
||||
return nil, errors.Wrap(core.ErrMissedArgument, "missed source expression")
|
||||
}
|
||||
@ -40,27 +34,59 @@ func NewForExpression(
|
||||
|
||||
return &ForExpression{
|
||||
src,
|
||||
valVar, keyVar,
|
||||
dataSource,
|
||||
predicate,
|
||||
distinct,
|
||||
spread,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (e *ForExpression) AddLimit(src core.SourceMap, size, count int) {
|
||||
e.dataSource = clauses.NewLimitClause(src, e.dataSource, size, count)
|
||||
func (e *ForExpression) AddLimit(src core.SourceMap, size, count int) error {
|
||||
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) {
|
||||
e.dataSource = clauses.NewFilterClause(src, e.dataSource, e.valVar, e.keyVar, exp)
|
||||
func (e *ForExpression) AddFilter(src core.SourceMap, exp core.Expression) error {
|
||||
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) {
|
||||
e.dataSource = clauses.NewSortClause(src, e.dataSource, e.valVar, sorters...)
|
||||
func (e *ForExpression) AddSort(src core.SourceMap, sorters ...*clauses.SorterExpression) error {
|
||||
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) {
|
||||
e.dataSource = clauses.NewDistinctClause(src, e.dataSource)
|
||||
func (e *ForExpression) AddCollect(src core.SourceMap, params *clauses.Collect) error {
|
||||
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) {
|
||||
@ -70,20 +96,27 @@ func (e *ForExpression) Exec(ctx context.Context, scope *core.Scope) (core.Value
|
||||
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)
|
||||
variables := e.dataSource.Variables()
|
||||
|
||||
for iterator.HasNext() {
|
||||
val, key, err := iterator.Next()
|
||||
ds, err := iterator.Next()
|
||||
|
||||
if err != nil {
|
||||
return values.None, core.SourceError(e.src, err)
|
||||
}
|
||||
|
||||
innerScope := scope.Fork()
|
||||
innerScope.SetVariable(e.valVar, val)
|
||||
|
||||
if e.keyVar != "" {
|
||||
innerScope.SetVariable(e.keyVar, key)
|
||||
if err := ds.Apply(innerScope, variables); err != nil {
|
||||
return values.None, err
|
||||
}
|
||||
|
||||
out, err := e.predicate.Exec(ctx, innerScope)
|
||||
@ -92,20 +125,39 @@ func (e *ForExpression) Exec(ctx context.Context, scope *core.Scope) (core.Value
|
||||
return values.None, err
|
||||
}
|
||||
|
||||
if !e.spread {
|
||||
res.Push(out)
|
||||
var add bool
|
||||
|
||||
// The result shouldn't be distinct
|
||||
// Just add the output
|
||||
if !e.distinct {
|
||||
add = true
|
||||
} else {
|
||||
elements, ok := out.(*values.Array)
|
||||
// We need to check whether the value already exists in the result set
|
||||
hash := out.Hash()
|
||||
_, exists := hashTable[hash]
|
||||
|
||||
if !ok {
|
||||
return values.None, core.Error(core.ErrInvalidOperation, "spread of non-array value")
|
||||
if !exists {
|
||||
hashTable[hash] = true
|
||||
add = true
|
||||
}
|
||||
}
|
||||
|
||||
elements.ForEach(func(i core.Value, _ int) bool {
|
||||
res.Push(i)
|
||||
if add {
|
||||
if !e.spread {
|
||||
res.Push(out)
|
||||
} else {
|
||||
elements, ok := out.(*values.Array)
|
||||
|
||||
return true
|
||||
})
|
||||
if !ok {
|
||||
return values.None, core.Error(core.ErrInvalidOperation, "spread of non-array value")
|
||||
}
|
||||
|
||||
elements.ForEach(func(i core.Value, _ int) bool {
|
||||
res.Push(i)
|
||||
|
||||
return true
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2,7 +2,6 @@ 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"
|
||||
)
|
||||
@ -25,20 +24,12 @@ func NewFunctionCallExpression(
|
||||
return &FunctionCallExpression{src, fun, args}, nil
|
||||
}
|
||||
|
||||
func (e *FunctionCallExpression) Iterate(ctx context.Context, scope *core.Scope) (collections.Iterator, error) {
|
||||
value, err := e.Exec(ctx, scope)
|
||||
func (e *FunctionCallExpression) Arguments() []core.Expression {
|
||||
return e.args
|
||||
}
|
||||
|
||||
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 *FunctionCallExpression) Function() core.Function {
|
||||
return e.fun
|
||||
}
|
||||
|
||||
func (e *FunctionCallExpression) Exec(ctx context.Context, scope *core.Scope) (core.Value, error) {
|
||||
|
@ -2,7 +2,6 @@ package literals
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/collections"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/core"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/values"
|
||||
)
|
||||
@ -23,34 +22,14 @@ func (l *ArrayLiteral) Push(expression core.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) {
|
||||
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))
|
||||
|
||||
for _, el := range l.elements {
|
||||
val, err := el.Exec(ctx, scope)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return values.None, err
|
||||
}
|
||||
|
||||
arr.Push(val)
|
||||
|
@ -2,7 +2,6 @@ package literals
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/collections"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/core"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/values"
|
||||
)
|
||||
@ -18,56 +17,40 @@ type (
|
||||
}
|
||||
)
|
||||
|
||||
func NewObjectPropertyAssignment(name, value core.Expression) *ObjectPropertyAssignment {
|
||||
return &ObjectPropertyAssignment{name, value}
|
||||
}
|
||||
func NewObjectPropertyAssignment(name, value core.Expression) (*ObjectPropertyAssignment, error) {
|
||||
if name == nil {
|
||||
return nil, core.Error(core.ErrMissedArgument, "property name expression")
|
||||
}
|
||||
|
||||
func NewObjectLiteral() *ObjectLiteral {
|
||||
return &ObjectLiteral{make([]*ObjectPropertyAssignment, 0, 10)}
|
||||
if value == nil {
|
||||
return nil, core.Error(core.ErrMissedArgument, "property value expression")
|
||||
}
|
||||
|
||||
return &ObjectPropertyAssignment{name, value}, nil
|
||||
}
|
||||
|
||||
func NewObjectLiteralWith(props ...*ObjectPropertyAssignment) *ObjectLiteral {
|
||||
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) {
|
||||
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()
|
||||
|
||||
for _, el := range l.properties {
|
||||
name, err := el.name.Exec(ctx, scope)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return values.None, err
|
||||
}
|
||||
|
||||
val, err := el.value.Exec(ctx, scope)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return values.None, err
|
||||
}
|
||||
|
||||
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)
|
||||
|
@ -2,7 +2,6 @@ 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"
|
||||
"github.com/pkg/errors"
|
||||
@ -26,22 +25,6 @@ func NewMemberExpression(src core.SourceMap, variableName string, path []core.Ex
|
||||
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) {
|
||||
val, err := scope.GetVariable(e.variableName)
|
||||
|
||||
|
@ -2,7 +2,6 @@ package operators
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/collections"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/core"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/values"
|
||||
)
|
||||
@ -27,16 +26,6 @@ func NewRangeOperator(
|
||||
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) {
|
||||
left, err := operator.left.Exec(ctx, scope)
|
||||
|
||||
|
@ -2,7 +2,6 @@ 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"
|
||||
)
|
||||
@ -20,22 +19,6 @@ func NewParameterExpression(src core.SourceMap, name string) (*ParameterExpressi
|
||||
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) {
|
||||
param, err := core.ParamFrom(ctx, e.name)
|
||||
|
||||
|
@ -2,7 +2,6 @@ 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"
|
||||
"github.com/pkg/errors"
|
||||
@ -42,22 +41,6 @@ func NewVariableDeclarationExpression(src core.SourceMap, name string, init core
|
||||
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) {
|
||||
return scope.GetVariable(e.name)
|
||||
}
|
||||
|
@ -279,6 +279,17 @@ func Unmarshal(value json.RawMessage) (core.Value, error) {
|
||||
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 {
|
||||
switch input.Type() {
|
||||
case core.BooleanType:
|
||||
@ -296,13 +307,54 @@ func ToBoolean(input core.Value) core.Value {
|
||||
}
|
||||
}
|
||||
|
||||
func IsCloneable(value core.Value) Boolean {
|
||||
switch value.Type() {
|
||||
func ToArray(input core.Value) core.Value {
|
||||
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:
|
||||
return NewBoolean(true)
|
||||
return input.Copy()
|
||||
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:
|
||||
return NewBoolean(false)
|
||||
return NewArray(0)
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,10 @@
|
||||
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 {
|
||||
return map[string]core.Function{
|
||||
@ -29,3 +33,19 @@ func NewLib() map[string]core.Function {
|
||||
"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
|
||||
}
|
||||
|
||||
sorter, err := collections.NewSorter(func(first core.Value, second core.Value) (int, error) {
|
||||
return first.Compare(second), nil
|
||||
sorter, err := collections.NewSorter(func(first collections.DataSet, second collections.DataSet) (int, error) {
|
||||
return first.Get(collections.DefaultValueVar).Compare(second.Get(collections.DefaultValueVar)), nil
|
||||
}, collections.SortDirectionAsc)
|
||||
|
||||
if err != nil {
|
||||
@ -40,7 +40,7 @@ func Sorted(_ context.Context, args ...core.Value) (core.Value, error) {
|
||||
}
|
||||
|
||||
iterator, err := collections.NewSortIterator(
|
||||
collections.NewArrayIterator(arr),
|
||||
collections.NewDefaultIndexedIterator(arr),
|
||||
sorter,
|
||||
)
|
||||
|
||||
@ -48,5 +48,5 @@ func Sorted(_ context.Context, args ...core.Value) (core.Value, error) {
|
||||
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
|
||||
}
|
||||
|
||||
sorter, err := collections.NewSorter(func(first core.Value, second core.Value) (int, error) {
|
||||
return first.Compare(second), nil
|
||||
sorter, err := collections.NewSorter(func(first collections.DataSet, second collections.DataSet) (int, error) {
|
||||
return first.Get(collections.DefaultValueVar).Compare(second.Get(collections.DefaultValueVar)), nil
|
||||
}, collections.SortDirectionAsc)
|
||||
|
||||
if err != nil {
|
||||
return values.None, err
|
||||
}
|
||||
|
||||
uniqIterator, err := collections.NewUniqueIterator(collections.NewArrayIterator(arr))
|
||||
uniqIterator, err := collections.NewUniqueIterator(
|
||||
collections.NewDefaultIndexedIterator(arr),
|
||||
collections.DefaultValueVar,
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return values.None, err
|
||||
@ -55,5 +58,5 @@ func SortedUnique(_ context.Context, args ...core.Value) (core.Value, error) {
|
||||
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(
|
||||
collections.NewArrayIterator(arr),
|
||||
collections.NewDefaultIndexedIterator(arr),
|
||||
collections.DefaultValueVar,
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return values.None, err
|
||||
}
|
||||
|
||||
return collections.ToArray(iterator)
|
||||
return toArray(iterator)
|
||||
}
|
||||
|
@ -2,13 +2,11 @@ package types
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/MontFerret/ferret/pkg/runtime/collections"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/core"
|
||||
"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.
|
||||
// @returns (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]
|
||||
|
||||
switch arg.Type() {
|
||||
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
|
||||
}
|
||||
return values.ToArray(arg), nil
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user