From 34be9589f709da6e4e2ac855dba4e4ba1de99d53 Mon Sep 17 00:00:00 2001 From: Daniel Richter Date: Tue, 19 Sep 2023 19:56:51 -0700 Subject: [PATCH 1/3] Fix single element "in" clauses --- evaluationStage.go | 14 ++++++++++++++ evaluation_test.go | 6 ++++++ stagePlanner.go | 7 +++++++ 3 files changed, 27 insertions(+) diff --git a/evaluationStage.go b/evaluationStage.go index 11ea587..87d54b6 100644 --- a/evaluationStage.go +++ b/evaluationStage.go @@ -404,6 +404,20 @@ func makeAccessorStage(pair []string) evaluationOperator { } } +func ensureSliceStage(op evaluationOperator) evaluationOperator { + return func(left interface{}, right interface{}, parameters Parameters) (interface{}, error) { + orig, err := op(left, right, parameters) + if err != nil { + return orig, err + } + slice, isSlice := orig.([]interface{}) + if !isSlice { + slice = []interface{}{orig} + } + return slice, nil + } +} + func separatorStage(left interface{}, right interface{}, parameters Parameters) (interface{}, error) { var ret []interface{} diff --git a/evaluation_test.go b/evaluation_test.go index a2b65e8..c60ed2c 100644 --- a/evaluation_test.go +++ b/evaluation_test.go @@ -505,6 +505,12 @@ func TestNoParameterEvaluation(test *testing.T) { Input: "!(1 in (1, 2, 3))", Expected: false, }, + EvaluationTest{ + + Name: "Single Element Array membership literal", + Input: "1 in (1)", + Expected: true, + }, EvaluationTest{ Name: "Logical operator reordering (#30)", diff --git a/stagePlanner.go b/stagePlanner.go index d71ed12..e9d93af 100644 --- a/stagePlanner.go +++ b/stagePlanner.go @@ -385,12 +385,19 @@ func planValue(stream *tokenStream) (*evaluationStage, error) { switch token.Kind { case CLAUSE: + prev := stream.tokens[stream.index-2] // todo guard against underflow ret, err = planTokens(stream) if err != nil { return nil, err } + // clauses with single elements don't trigger SEPARATE stage planner + // this ensures that when used as part of an "in" comparison, the array requirement passes + if prev.Kind == COMPARATOR && prev.Value == "in" && ret.symbol == LITERAL { + ret.operator = ensureSliceStage(ret.operator) + } + // advance past the CLAUSE_CLOSE token. We know that it's a CLAUSE_CLOSE, because at parse-time we check for unbalanced parens. stream.next() From f3f94581651cc5dca7ef1ad66a1ea277996b9e05 Mon Sep 17 00:00:00 2001 From: Daniel Richter Date: Tue, 19 Sep 2023 20:03:42 -0700 Subject: [PATCH 2/3] Guard against invalid slice access --- stagePlanner.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/stagePlanner.go b/stagePlanner.go index e9d93af..1cccb73 100644 --- a/stagePlanner.go +++ b/stagePlanner.go @@ -385,7 +385,10 @@ func planValue(stream *tokenStream) (*evaluationStage, error) { switch token.Kind { case CLAUSE: - prev := stream.tokens[stream.index-2] // todo guard against underflow + var prev ExpressionToken + if stream.index > 1 { + prev = stream.tokens[stream.index-2] + } ret, err = planTokens(stream) if err != nil { From 4947a13e2e19ea527b84299eba4399453e01325c Mon Sep 17 00:00:00 2001 From: Daniel Richter Date: Tue, 19 Sep 2023 20:05:54 -0700 Subject: [PATCH 3/3] Always return a slice --- evaluationStage.go | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/evaluationStage.go b/evaluationStage.go index 87d54b6..a90b97a 100644 --- a/evaluationStage.go +++ b/evaluationStage.go @@ -410,11 +410,7 @@ func ensureSliceStage(op evaluationOperator) evaluationOperator { if err != nil { return orig, err } - slice, isSlice := orig.([]interface{}) - if !isSlice { - slice = []interface{}{orig} - } - return slice, nil + return []interface{}{orig}, nil } }