1
0
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:
Tim Voronov
2018-11-12 19:58:12 -05:00
committed by GitHub
parent de774ba03e
commit 291d07cbef
18 changed files with 1275 additions and 720 deletions

View File

@@ -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
}

View File

@@ -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 {

View File

@@ -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)
}

View File

@@ -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:

View 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()))
})
})
}

View File

@@ -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 {