Browse Source

feat(index):support SELECT sli[-1] FROM

EMqmyd 4 years ago
parent
commit
df98e127f5
6 changed files with 148 additions and 24 deletions
  1. 34 4
      docs/en_US/sqls/json_expr.md
  2. 40 4
      docs/zh_CN/sqls/json_expr.md
  3. 23 9
      xsql/ast.go
  4. 3 2
      xsql/parser.go
  5. 6 5
      xsql/parser_test.go
  6. 42 0
      xsql/valuer_eval_test.go

+ 34 - 4
docs/en_US/sqls/json_expr.md

@@ -58,7 +58,7 @@ SELECT name->first AS fname FROM demo
 
 ### Index expression
 
-Index Expressions allow you to select a specific element in a list. It should look similar to array access in common programming languages. Indexing is 0 based.
+Index Expressions allow you to select a specific element in a list. It should look similar to array access in common programming languages.The index value starts with 0, -1 is the starting position from the end, and so on.
 
 ```
 SELECT children FROM demo
@@ -77,6 +77,24 @@ SELECT children[0] FROM demo
     "children": "Sara"
 }
 
+SELECT children[1] FROM demo
+
+{
+    "children": "Alex"
+}
+
+SELECT children[-1] FROM demo
+
+{
+    "children": "Jack"
+}
+
+SELECT children[-2] FROM demo
+
+{
+    "children": "Alex"
+}
+
 SELECT d.friends[0]->last FROM demo AS d
 
 {
@@ -88,12 +106,24 @@ SELECT d.friends[0]->last FROM demo AS d
 
 Slices allow you to select a contiguous subset of an array. 
 
-``field[from:to]`` If from is not specified, then it means start from the 1st element of array; If to is not specified, then it means end with the last element of array.
+``field[from:to)``is the interval before closing and opening, excluding to. If from is not specified, then it means start from the 1st element of array; If to is not specified, then it means end with the last element of array.
 
 ```
 SELECT children[0:1] FROM demo
 
 {
+    "children": ["Sara"]
+}
+
+SELECT children[1:-1] FROM demo
+
+{
+    "children": ["Alex"]
+}
+
+SELECT children[0:-1] FROM demo
+
+{
     "children": ["Sara","Alex"]
 }
 ```
@@ -111,7 +141,7 @@ SELECT children[:] FROM demo == SELECT children FROM demo
 
 
 ```
-SELECT children[:1] FROM demo
+SELECT children[:2] FROM demo
 
 {
     "children": ["Sara","Alex"]
@@ -124,7 +154,7 @@ SELECT children[:1] FROM demo
 SELECT followers->Group1[:1]->first FROM demo
 
 {
-    "first": ["John","Alice"]
+    "first": ["John"]
 }
 ```
 

+ 40 - 4
docs/zh_CN/sqls/json_expr.md

@@ -60,7 +60,7 @@ SELECT name->first AS fname FROM demo
 
 ### 索引表达式
 
-索引表达式使您可以选择列表中的特定元素。 它看起来应该类似于普通编程语言中的数组访问。 索引从0开始
+索引表达式使您可以选择列表中的特定元素。 它看起来应该类似于普通编程语言中的数组访问。 索引值以0为开始值,-1 为从末尾的开始位置,以此类推
 
 ```
 SELECT children FROM demo
@@ -79,6 +79,24 @@ SELECT children[0] FROM demo
     "children": "Sara"
 }
 
+SELECT children[1] FROM demo
+
+{
+    "children": "Alex"
+}
+
+SELECT children[-1] FROM demo
+
+{
+    "children": "Jack"
+}
+
+SELECT children[-2] FROM demo
+
+{
+    "children": "Alex"
+}
+
 SELECT d.friends[0]->last FROM demo AS d
 
 {
@@ -90,12 +108,24 @@ SELECT d.friends[0]->last FROM demo AS d
 
 切片允许您选择数组的连续子集。
 
-`field[from:to]` 如果未指定 from,则表示从数组的第一个元素开始; 如果未指定 to,则表示以数组的最后一个元素结尾。
+`field[from:to)` 为前闭后开区间,不包含to。如果未指定 from,则表示从数组的第一个元素开始; 如果未指定 to,则表示以数组的最后一个元素结尾。
 
 ```
 SELECT children[0:1] FROM demo
 
 {
+    "children": ["Sara"]
+}
+
+SELECT children[1:-1] FROM demo
+
+{
+    "children": ["Alex"]
+}
+
+SELECT children[0:-1] FROM demo
+
+{
     "children": ["Sara","Alex"]
 }
 ```
@@ -113,7 +143,13 @@ SELECT children[:] FROM demo == SELECT children FROM demo
 
 
 ```
-SELECT children[:1] FROM demo
+SELECT children[:2] FROM demo
+
+{
+    "children": ["Sara","Alex"]
+}
+
+SELECT children[:-1] FROM demo
 
 {
     "children": ["Sara","Alex"]
@@ -126,7 +162,7 @@ SELECT children[:1] FROM demo
 SELECT followers->Group1[:1]->first FROM demo
 
 {
-    "first": ["John","Alice"]
+    "first": ["John"]
 }
 ```
 

+ 23 - 9
xsql/ast.go

@@ -1199,23 +1199,37 @@ func (v *ValuerEval) subset(result interface{}, expr Expr) interface{} {
 	ber := v.Eval(expr)
 	if berVal, ok1 := ber.(*BracketEvalResult); ok1 {
 		if berVal.isIndex() {
-			if berVal.Start >= val.Len() {
+			if 0 > berVal.Start {
+				if 0 > berVal.Start+val.Len() {
+					return fmt.Errorf("out of index: %d of %d", berVal.Start, val.Len())
+				}
+				berVal.Start += val.Len()
+			} else if berVal.Start >= val.Len() {
 				return fmt.Errorf("out of index: %d of %d", berVal.Start, val.Len())
 			}
 			return val.Index(berVal.Start).Interface()
 		} else {
-			if berVal.Start >= val.Len() {
+			if 0 > berVal.Start {
+				if 0 > berVal.Start+val.Len() {
+					return fmt.Errorf("out of index: %d of %d", berVal.Start, val.Len())
+				}
+				berVal.Start += val.Len()
+			} else if berVal.Start >= val.Len() {
 				return fmt.Errorf("start value is out of index: %d of %d", berVal.Start, val.Len())
 			}
-
-			if berVal.End >= val.Len() {
+			if math.MinInt32 == berVal.End {
+				berVal.End = val.Len()
+			} else if 0 > berVal.End {
+				if 0 > berVal.End+val.Len() {
+					return fmt.Errorf("out of index: %d of %d", berVal.End, val.Len())
+				}
+				berVal.End += val.Len()
+			} else if berVal.End > val.Len() {
 				return fmt.Errorf("end value is out of index: %d of %d", berVal.End, val.Len())
+			} else if berVal.Start >= berVal.End {
+				return fmt.Errorf("start cannot be greater than end. start:%d  end:%d", berVal.Start, berVal.End)
 			}
-			end := berVal.End
-			if end == -1 {
-				end = val.Len()
-			}
-			return val.Slice(berVal.Start, end).Interface()
+			return val.Slice(berVal.Start, berVal.End).Interface()
 		}
 	} else {
 		return fmt.Errorf("invalid evaluation result - %v", berVal)

+ 3 - 2
xsql/parser.go

@@ -5,6 +5,7 @@ import (
 	"github.com/emqx/kuiper/common"
 	"github.com/golang-collections/collections/stack"
 	"io"
+	"math"
 	"strconv"
 	"strings"
 )
@@ -547,7 +548,7 @@ func (p *Parser) parseBracketExpr() (Expr, error) {
 	tok2, lit2 := p.scanIgnoreWhitespace()
 	if tok2 == RBRACKET {
 		//field[]
-		return &ColonExpr{Start: 0, End: -1}, nil
+		return &ColonExpr{Start: 0, End: math.MinInt32}, nil
 	} else if tok2 == INTEGER {
 		start, err := strconv.Atoi(lit2)
 		if err != nil {
@@ -581,7 +582,7 @@ func (p *Parser) parseColonExpr(start int) (Expr, error) {
 			return nil, fmt.Errorf("Found %q, expected right bracket.", lit1)
 		}
 	} else if tok == RBRACKET {
-		return &ColonExpr{Start: start, End: -1}, nil
+		return &ColonExpr{Start: start, End: math.MinInt32}, nil
 	}
 	return nil, fmt.Errorf("Found %q, expected right bracket.", lit)
 }

+ 6 - 5
xsql/parser_test.go

@@ -2,6 +2,7 @@ package xsql
 
 import (
 	"fmt"
+	"math"
 	"reflect"
 	"strings"
 	"testing"
@@ -1672,7 +1673,7 @@ func TestParser_ParseJsonExpr(t *testing.T) {
 						Expr: &BinaryExpr{
 							LHS: &FieldRef{Name: "children"},
 							OP:  SUBSET,
-							RHS: &ColonExpr{Start: 0, End: -1},
+							RHS: &ColonExpr{Start: 0, End: math.MinInt32},
 						},
 						Name:  "",
 						AName: ""},
@@ -1689,7 +1690,7 @@ func TestParser_ParseJsonExpr(t *testing.T) {
 						Expr: &BinaryExpr{
 							LHS: &FieldRef{Name: "children"},
 							OP:  SUBSET,
-							RHS: &ColonExpr{Start: 2, End: -1},
+							RHS: &ColonExpr{Start: 2, End: math.MinInt32},
 						},
 						Name:  "",
 						AName: "c"},
@@ -1704,7 +1705,7 @@ func TestParser_ParseJsonExpr(t *testing.T) {
 				Fields: []Field{
 					{
 						Expr: &BinaryExpr{
-							LHS: &BinaryExpr{LHS: &FieldRef{Name: "children"}, OP: SUBSET, RHS: &ColonExpr{Start: 2, End: -1}},
+							LHS: &BinaryExpr{LHS: &FieldRef{Name: "children"}, OP: SUBSET, RHS: &ColonExpr{Start: 2, End: math.MinInt32}},
 							OP:  ARROW,
 							RHS: &FieldRef{Name: "first"},
 						},
@@ -1734,7 +1735,7 @@ func TestParser_ParseJsonExpr(t *testing.T) {
 				Fields: []Field{
 					{
 						Expr: &BinaryExpr{
-							LHS: &BinaryExpr{LHS: &FieldRef{StreamName: StreamName("demo"), Name: "children"}, OP: SUBSET, RHS: &ColonExpr{Start: 2, End: -1}},
+							LHS: &BinaryExpr{LHS: &FieldRef{StreamName: StreamName("demo"), Name: "children"}, OP: SUBSET, RHS: &ColonExpr{Start: 2, End: math.MinInt32}},
 							OP:  ARROW,
 							RHS: &FieldRef{Name: "first"},
 						},
@@ -1754,7 +1755,7 @@ func TestParser_ParseJsonExpr(t *testing.T) {
 							Name: "lower",
 							Args: []Expr{
 								&BinaryExpr{
-									LHS: &BinaryExpr{LHS: &FieldRef{StreamName: StreamName("demo"), Name: "children"}, OP: SUBSET, RHS: &ColonExpr{Start: 2, End: -1}},
+									LHS: &BinaryExpr{LHS: &FieldRef{StreamName: StreamName("demo"), Name: "children"}, OP: SUBSET, RHS: &ColonExpr{Start: 2, End: math.MinInt32}},
 									OP:  ARROW,
 									RHS: &FieldRef{Name: "first"},
 								},

+ 42 - 0
xsql/valuer_eval_test.go

@@ -226,7 +226,49 @@ func TestCalculation(t *testing.T) {
 		stmt, _ := NewParser(strings.NewReader(sql)).Parse()
 		projects = append(projects, stmt.Fields[0].Expr)
 	}
+	fmt.Printf("The test bucket size is %d.\n\n", len(data)*len(sqls))
+	for i, tt := range data {
+		for j, c := range projects {
+			tuple := &Tuple{Emitter: "src", Message: tt.m, Timestamp: common.GetNowInMilli(), Metadata: nil}
+			ve := &ValuerEval{Valuer: MultiValuer(tuple)}
+			result := ve.Eval(c)
+			if !reflect.DeepEqual(tt.r[j], result) {
+				t.Errorf("%d-%d. \nstmt mismatch:\n\nexp=%#v\n\ngot=%#v\n\n", i, j, tt.r[j], result)
+			}
+		}
+	}
+}
 
+func TestArray(t *testing.T) {
+	data := []struct {
+		m Message
+		r []interface{}
+	}{
+		{
+			m: map[string]interface{}{
+				"a": []int64{0, 1, 2, 3, 4, 5},
+			},
+			r: []interface{}{
+				int64(0), int64(5), int64(1), []int64{0, 1}, []int64{4, 5}, []int64{5}, []int64{0, 1, 2, 3, 4}, []int64{1, 2, 3, 4}, []int64{0, 1, 2, 3, 4, 5},
+			},
+		},
+	}
+	sqls := []string{
+		"select a[0] as t from src",
+		"select a[-1] as t from src",
+		"select a[1] as t from src",
+		"select a[:2] as t from src",
+		"select a[4:] as t from src",
+		"select a[-1:] as t from src",
+		"select a[0:-1] as t from src",
+		"select a[-5:-1] as t from src",
+		"select a[:] as t from src",
+	}
+	var projects []Expr
+	for _, sql := range sqls {
+		stmt, _ := NewParser(strings.NewReader(sql)).Parse()
+		projects = append(projects, stmt.Fields[0].Expr)
+	}
 	fmt.Printf("The test bucket size is %d.\n\n", len(data)*len(sqls))
 	for i, tt := range data {
 		for j, c := range projects {