mirror of
https://github.com/MontFerret/ferret.git
synced 2025-11-06 08:39:09 +02:00
Feature/custom iterator (#173)
* Added CollectionIterator interface * Added PAGINATION function * Fixed LIMIT clause * Fixed linting issues
This commit is contained in:
@@ -1,12 +1,14 @@
|
||||
package collections
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/core"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/values"
|
||||
)
|
||||
|
||||
type (
|
||||
Collection interface {
|
||||
core.Value
|
||||
Length() values.Int
|
||||
}
|
||||
|
||||
@@ -22,4 +24,54 @@ type (
|
||||
Get(key values.String) (core.Value, values.Boolean)
|
||||
Set(key values.String, value core.Value)
|
||||
}
|
||||
|
||||
IterableCollection interface {
|
||||
core.Value
|
||||
Iterate(ctx context.Context) (CollectionIterator, error)
|
||||
}
|
||||
|
||||
CollectionIterator interface {
|
||||
Next(ctx context.Context) (value core.Value, key core.Value, err error)
|
||||
}
|
||||
|
||||
collectionIteratorWrapper struct {
|
||||
valVar string
|
||||
keyVar string
|
||||
values CollectionIterator
|
||||
}
|
||||
)
|
||||
|
||||
func NewCollectionIterator(
|
||||
valVar,
|
||||
keyVar string,
|
||||
values CollectionIterator,
|
||||
) (Iterator, error) {
|
||||
return &collectionIteratorWrapper{valVar, keyVar, values}, nil
|
||||
}
|
||||
|
||||
func (iterator *collectionIteratorWrapper) Next(ctx context.Context, scope *core.Scope) (*core.Scope, error) {
|
||||
val, key, err := iterator.values.Next(ctx)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// end of iteration
|
||||
if val == values.None {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
nextScope := scope.Fork()
|
||||
|
||||
if err := nextScope.SetVariable(iterator.valVar, val); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if iterator.keyVar != "" {
|
||||
if err := nextScope.SetVariable(iterator.keyVar, key); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return nextScope, nil
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ const (
|
||||
HTMLElementType Type = 8
|
||||
HTMLDocumentType Type = 9
|
||||
BinaryType Type = 10
|
||||
CustomType Type = 99
|
||||
)
|
||||
|
||||
var typestr = map[Type]string{
|
||||
@@ -35,6 +36,7 @@ var typestr = map[Type]string{
|
||||
HTMLElementType: "HTMLElement",
|
||||
HTMLDocumentType: "HTMLDocument",
|
||||
BinaryType: "BinaryType",
|
||||
CustomType: "CustomType",
|
||||
}
|
||||
|
||||
func (t Type) String() string {
|
||||
|
||||
@@ -9,15 +9,15 @@ import (
|
||||
type LimitClause struct {
|
||||
src core.SourceMap
|
||||
dataSource collections.Iterable
|
||||
count int
|
||||
offset int
|
||||
count core.Expression
|
||||
offset core.Expression
|
||||
}
|
||||
|
||||
func NewLimitClause(
|
||||
src core.SourceMap,
|
||||
dataSource collections.Iterable,
|
||||
count int,
|
||||
offset int,
|
||||
count core.Expression,
|
||||
offset core.Expression,
|
||||
) (collections.Iterable, error) {
|
||||
if dataSource == nil {
|
||||
return nil, core.Error(core.ErrMissedArgument, "dataSource source")
|
||||
@@ -33,10 +33,34 @@ func (clause *LimitClause) Iterate(ctx context.Context, scope *core.Scope) (coll
|
||||
return nil, core.SourceError(clause.src, err)
|
||||
}
|
||||
|
||||
count, err := clause.count.Exec(ctx, scope)
|
||||
|
||||
if err != nil {
|
||||
return nil, core.SourceError(clause.src, err)
|
||||
}
|
||||
|
||||
offset, err := clause.offset.Exec(ctx, scope)
|
||||
|
||||
if err != nil {
|
||||
return nil, core.SourceError(clause.src, err)
|
||||
}
|
||||
|
||||
countInt, err := clause.parseValue(count)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
offsetInt, err := clause.parseValue(offset)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
iterator, err := collections.NewLimitIterator(
|
||||
src,
|
||||
clause.count,
|
||||
clause.offset,
|
||||
int(countInt),
|
||||
int(offsetInt),
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
@@ -45,3 +69,15 @@ func (clause *LimitClause) Iterate(ctx context.Context, scope *core.Scope) (coll
|
||||
|
||||
return iterator, nil
|
||||
}
|
||||
|
||||
func (clause *LimitClause) parseValue(val core.Value) (int, error) {
|
||||
if val.Type() == core.IntType {
|
||||
return val.Unwrap().(int), nil
|
||||
}
|
||||
|
||||
if val.Type() == core.FloatType {
|
||||
return int(val.Unwrap().(float64)), nil
|
||||
}
|
||||
|
||||
return -1, core.TypeError(val.Type(), core.IntType, core.FloatType)
|
||||
}
|
||||
|
||||
@@ -49,6 +49,15 @@ func (ds *DataSource) Iterate(ctx context.Context, scope *core.Scope) (collectio
|
||||
default:
|
||||
// fallback to user defined types
|
||||
switch data.(type) {
|
||||
case collections.IterableCollection:
|
||||
collection := data.(collections.IterableCollection)
|
||||
iterator, err := collection.Iterate(ctx)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return collections.NewCollectionIterator(ds.valVariable, ds.keyVariable, iterator)
|
||||
case collections.KeyedCollection:
|
||||
return collections.NewIndexedIterator(ds.valVariable, ds.keyVariable, data.(collections.IndexedCollection))
|
||||
case collections.IndexedCollection:
|
||||
|
||||
126
pkg/runtime/expressions/data_source_test.go
Normal file
126
pkg/runtime/expressions/data_source_test.go
Normal file
@@ -0,0 +1,126 @@
|
||||
package expressions_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/expressions"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/values"
|
||||
"testing"
|
||||
|
||||
"github.com/MontFerret/ferret/pkg/runtime/collections"
|
||||
"github.com/MontFerret/ferret/pkg/runtime/core"
|
||||
. "github.com/smartystreets/goconvey/convey"
|
||||
)
|
||||
|
||||
type (
|
||||
testIterableCollection struct {
|
||||
values collections.IndexedCollection
|
||||
}
|
||||
|
||||
testCollectionIterator struct {
|
||||
values collections.IndexedCollection
|
||||
position values.Int
|
||||
}
|
||||
|
||||
TestDataSourceExpression func(ctx context.Context, scope *core.Scope) (core.Value, error)
|
||||
)
|
||||
|
||||
func (ds TestDataSourceExpression) Exec(ctx context.Context, scope *core.Scope) (core.Value, error) {
|
||||
return ds(ctx, scope)
|
||||
}
|
||||
|
||||
func (c *testIterableCollection) MarshalJSON() ([]byte, error) {
|
||||
return nil, core.ErrInvalidOperation
|
||||
}
|
||||
func (c *testIterableCollection) Type() core.Type {
|
||||
return core.Type(11)
|
||||
}
|
||||
func (c *testIterableCollection) String() string {
|
||||
return ""
|
||||
}
|
||||
func (c *testIterableCollection) Compare(other core.Value) int {
|
||||
return 1
|
||||
}
|
||||
func (c *testIterableCollection) Unwrap() interface{} {
|
||||
return nil
|
||||
}
|
||||
func (c *testIterableCollection) Hash() uint64 {
|
||||
return 0
|
||||
}
|
||||
func (c *testIterableCollection) Copy() core.Value {
|
||||
return c
|
||||
}
|
||||
func (c *testIterableCollection) Iterate(ctx context.Context) (collections.CollectionIterator, error) {
|
||||
return &testCollectionIterator{c.values, -1}, nil
|
||||
}
|
||||
|
||||
func (i *testCollectionIterator) Next(ctx context.Context) (core.Value, core.Value, error) {
|
||||
i.position++
|
||||
|
||||
if i.position > i.values.Length() {
|
||||
return values.None, values.None, nil
|
||||
}
|
||||
|
||||
return i.values.Get(i.position), i.position, nil
|
||||
}
|
||||
|
||||
func TestDataSource(t *testing.T) {
|
||||
Convey(".Iterate", t, func() {
|
||||
Convey("Should return custom iterable collection", func() {
|
||||
arr := values.NewArrayWith(
|
||||
values.NewInt(1),
|
||||
values.NewInt(2),
|
||||
values.NewInt(3),
|
||||
values.NewInt(4),
|
||||
values.NewInt(5),
|
||||
values.NewInt(6),
|
||||
values.NewInt(7),
|
||||
values.NewInt(8),
|
||||
values.NewInt(9),
|
||||
values.NewInt(10),
|
||||
)
|
||||
|
||||
ds, err := expressions.NewDataSource(
|
||||
core.SourceMap{},
|
||||
collections.DefaultValueVar,
|
||||
collections.DefaultKeyVar,
|
||||
TestDataSourceExpression(func(ctx context.Context, scope *core.Scope) (core.Value, error) {
|
||||
return &testIterableCollection{arr}, nil
|
||||
}),
|
||||
)
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
rootScope, _ := core.NewRootScope()
|
||||
ctx := context.Background()
|
||||
scope := rootScope.Fork()
|
||||
out, err := ds.Iterate(ctx, scope)
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
pos := -1
|
||||
|
||||
nextScope := scope
|
||||
|
||||
for {
|
||||
pos++
|
||||
nextScope, err = out.Next(ctx, nextScope.Fork())
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
if nextScope == nil {
|
||||
break
|
||||
}
|
||||
|
||||
actualV, _ := nextScope.GetVariable(collections.DefaultValueVar)
|
||||
actualK, _ := nextScope.GetVariable(collections.DefaultKeyVar)
|
||||
|
||||
expectedV := arr.Get(values.Int(pos))
|
||||
|
||||
So(actualV, ShouldEqual, expectedV)
|
||||
So(actualK, ShouldEqual, values.Int(pos))
|
||||
}
|
||||
|
||||
So(pos, ShouldEqual, int(arr.Length()))
|
||||
})
|
||||
})
|
||||
}
|
||||
@@ -40,7 +40,7 @@ func NewForExpression(
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (e *ForExpression) AddLimit(src core.SourceMap, size, count int) error {
|
||||
func (e *ForExpression) AddLimit(src core.SourceMap, size, count core.Expression) error {
|
||||
limit, err := clauses.NewLimitClause(src, e.dataSource, size, count)
|
||||
|
||||
if err != nil {
|
||||
|
||||
Reference in New Issue
Block a user