Kaynağa Gözat

feat: support expression for array index in SelectStmt (#1750)

* support parser

Signed-off-by: yisaer <disxiaofei@163.com>

* add test

Signed-off-by: yisaer <disxiaofei@163.com>

* address the comment

Signed-off-by: yisaer <disxiaofei@163.com>

* add more testcases

Signed-off-by: yisaer <disxiaofei@163.com>

---------

Signed-off-by: yisaer <disxiaofei@163.com>
Song Gao 2 yıl önce
ebeveyn
işleme
327b578aa7

+ 3 - 1
internal/topo/topotest/mock_topo.go

@@ -1,4 +1,4 @@
-// Copyright 2021-2022 EMQ Technologies Co., Ltd.
+// Copyright 2021-2023 EMQ Technologies Co., Ltd.
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -268,6 +268,8 @@ func HandleStream(createOrDrop bool, names []string, t *testing.T) {
 		var sql string
 		if createOrDrop {
 			switch name {
+			case "demoArr":
+				sql = `CREATE STREAM demoArr () WITH (DATASOURCE="demoArr", TYPE="mock", FORMAT="json", KEY="ts");`
 			case "demo":
 				sql = `CREATE STREAM demo (
 					color STRING,

+ 12 - 1
internal/topo/topotest/mocknode/mock_data.go

@@ -1,4 +1,4 @@
-// Copyright 2021-2022 EMQ Technologies Co., Ltd.
+// Copyright 2021-2023 EMQ Technologies Co., Ltd.
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -1002,6 +1002,17 @@ var TestData = map[string][]*xsql.Tuple{
 			Timestamp: 1541152487501,
 		},
 	},
+	"demoArr": {
+		{
+			Emitter: "demoArr",
+			Message: map[string]interface{}{
+				"arr": []int{1, 2, 3, 4, 5},
+				"x":   1,
+				"y":   2,
+			},
+			Timestamp: 1541152487501,
+		},
+	},
 }
 
 var Image, _ = getImg()

+ 46 - 3
internal/topo/topotest/rule_test.go

@@ -1,4 +1,4 @@
-// Copyright 2021-2022 EMQ Technologies Co., Ltd.
+// Copyright 2021-2023 EMQ Technologies Co., Ltd.
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -16,18 +16,30 @@ package topotest
 
 import (
 	"encoding/json"
+	"testing"
+
 	"github.com/lf-edge/ekuiper/internal/topo/topotest/mocknode"
 	"github.com/lf-edge/ekuiper/pkg/api"
-	"testing"
 )
 
 func TestSingleSQL(t *testing.T) {
 	//Reset
-	streamList := []string{"demo", "demoError", "demo1", "table1", "demoTable"}
+	streamList := []string{"demo", "demoError", "demo1", "table1", "demoTable", "demoArr"}
 	HandleStream(false, streamList, t)
 	//Data setup
 	var tests = []RuleTest{
 		{
+			Name: `TestSingleSQLRule0`,
+			Sql:  `SELECT arr[x:y+1] as col1 FROM demoArr`,
+			R: [][]map[string]interface{}{
+				{{
+					"col1": []interface{}{
+						float64(2), float64(3),
+					},
+				}},
+			},
+		},
+		{
 			Name: `TestSingleSQLRule1`,
 			Sql:  `SELECT *, upper(color) FROM demo`,
 			R: [][]map[string]interface{}{
@@ -577,6 +589,37 @@ func TestSingleSQL(t *testing.T) {
 			},
 		},
 		{
+			Name: `TestSingleSQLRule17`,
+			Sql:  `SELECT arr[x:4] as col1 FROM demoArr`,
+			R: [][]map[string]interface{}{
+				{{
+					"col1": []interface{}{
+						float64(2), float64(3), float64(4),
+					},
+				}},
+			},
+		},
+		{
+			Name: `TestSingleSQLRule16`,
+			Sql:  `SELECT arr[1:y] as col1 FROM demoArr`,
+			R: [][]map[string]interface{}{
+				{{
+					"col1": []interface{}{
+						float64(2),
+					},
+				}},
+			},
+		},
+		{
+			Name: `TestSingleSQLRule15`,
+			Sql:  `SELECT arr[1] as col1 FROM demoArr`,
+			R: [][]map[string]interface{}{
+				{{
+					"col1": float64(2),
+				}},
+			},
+		},
+		{
 			Name: `TestLagAlias`,
 			Sql:  "SELECT lag(size) as lastSize, lag(had_changed(true,size)), size, lastSize/size as changeRate FROM demo WHERE size > 2",
 			R: [][]map[string]interface{}{

+ 11 - 2
internal/xsql/parser.go

@@ -1,4 +1,4 @@
-// Copyright 2022 EMQ Technologies Co., Ltd.
+// Copyright 2022-2023 EMQ Technologies Co., Ltd.
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -769,7 +769,16 @@ func (p *Parser) parseColonExpr(start ast.Expr) (ast.Expr, error) {
 	} else if tok == ast.RBRACKET {
 		return &ast.ColonExpr{Start: start, End: &ast.IntegerLiteral{Val: math.MinInt32}}, nil
 	}
-	return nil, fmt.Errorf("Found %q, expected right bracket.", lit)
+	p.unscan()
+	end, err := p.ParseExpr()
+	if err != nil {
+		return nil, fmt.Errorf("The end index %s is invalid in bracket expression.", lit)
+	}
+	if tok1, lit1 := p.scanIgnoreWhitespace(); tok1 == ast.RBRACKET {
+		return &ast.ColonExpr{Start: start, End: end}, nil
+	} else {
+		return nil, fmt.Errorf("Found %q, expected right bracket.", lit1)
+	}
 }
 
 func (p *Parser) scanIgnoreWhiteSpaceWithNegativeNum() (ast.Token, string) {

+ 117 - 1
internal/xsql/parser_test.go

@@ -1,4 +1,4 @@
-// Copyright 2021-2022 EMQ Technologies Co., Ltd.
+// Copyright 2021-2023 EMQ Technologies Co., Ltd.
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -36,6 +36,122 @@ func TestParser_ParseStatement(t *testing.T) {
 		err  string
 	}{
 		{
+			s: "SELECT arr[x:4] FROM tbl",
+			stmt: &ast.SelectStatement{
+				Fields: []ast.Field{
+					{
+						Expr: &ast.BinaryExpr{
+							OP: ast.SUBSET,
+							LHS: &ast.FieldRef{
+								Name:       "arr",
+								StreamName: ast.DefaultStream,
+							},
+							RHS: &ast.ColonExpr{
+								Start: &ast.FieldRef{
+									StreamName: ast.DefaultStream,
+									Name:       "x",
+								},
+								End: &ast.IntegerLiteral{
+									Val: 4,
+								},
+							},
+						},
+						Name:  "kuiper_field_0",
+						AName: "",
+					},
+				},
+				Sources: []ast.Source{&ast.Table{Name: "tbl"}},
+			},
+		},
+		{
+			s: "SELECT arr[1:x] FROM tbl",
+			stmt: &ast.SelectStatement{
+				Fields: []ast.Field{
+					{
+						Expr: &ast.BinaryExpr{
+							OP: ast.SUBSET,
+							LHS: &ast.FieldRef{
+								Name:       "arr",
+								StreamName: ast.DefaultStream,
+							},
+							RHS: &ast.ColonExpr{
+								Start: &ast.IntegerLiteral{
+									Val: 1,
+								},
+								End: &ast.FieldRef{
+									StreamName: ast.DefaultStream,
+									Name:       "x",
+								},
+							},
+						},
+						Name:  "kuiper_field_0",
+						AName: "",
+					},
+				},
+				Sources: []ast.Source{&ast.Table{Name: "tbl"}},
+			},
+		},
+		{
+			s: "SELECT arr[x] FROM tbl",
+			stmt: &ast.SelectStatement{
+				Fields: []ast.Field{
+					{
+						Expr: &ast.BinaryExpr{
+							OP: ast.SUBSET,
+							LHS: &ast.FieldRef{
+								Name:       "arr",
+								StreamName: ast.DefaultStream,
+							},
+							RHS: &ast.IndexExpr{
+								Index: &ast.FieldRef{
+									Name:       "x",
+									StreamName: ast.DefaultStream,
+								},
+							},
+						},
+						Name:  "kuiper_field_0",
+						AName: "",
+					},
+				},
+				Sources: []ast.Source{&ast.Table{Name: "tbl"}},
+			},
+		},
+		{
+			s: "SELECT arr[x+1:y] FROM tbl",
+			stmt: &ast.SelectStatement{
+				Fields: []ast.Field{
+					{
+						Expr: &ast.BinaryExpr{
+							OP: ast.SUBSET,
+							LHS: &ast.FieldRef{
+								Name:       "arr",
+								StreamName: ast.DefaultStream,
+							},
+							RHS: &ast.ColonExpr{
+								Start: &ast.BinaryExpr{
+									OP: ast.ADD,
+									LHS: &ast.FieldRef{
+										StreamName: ast.DefaultStream,
+										Name:       "x",
+									},
+									RHS: &ast.IntegerLiteral{
+										Val: 1,
+									},
+								},
+								End: &ast.FieldRef{
+									StreamName: ast.DefaultStream,
+									Name:       "y",
+								},
+							},
+						},
+						Name:  "kuiper_field_0",
+						AName: "",
+					},
+				},
+				Sources: []ast.Source{&ast.Table{Name: "tbl"}},
+			},
+		},
+		{
 			s: `SELECT name FROM tbl`,
 			stmt: &ast.SelectStatement{
 				Fields: []ast.Field{