浏览代码

Merge pull request #128 from emqx/develop

Develop
jinfahua 5 年之前
父节点
当前提交
22fb4caa89
共有 100 个文件被更改,包括 9143 次插入3077 次删除
  1. 31 1
      .github/workflows/release.yaml
  2. 61 0
      .github/workflows/fvt_tests.yaml
  3. 2 1
      .gitignore
  4. 66 1
      README-CN.md
  5. 66 1
      README.md
  6. 1 156
      common/data.go
  7. 189 0
      common/kv.go
  8. 2 3
      common/plugin_manager/manager.go
  9. 4 4
      common/templates/funcs.go
  10. 38 24
      common/time_util.go
  11. 35 0
      common/time_util_test.go
  12. 48 199
      common/util.go
  13. 6 6
      common/util_test.go
  14. 1 1
      deploy/docker/Dockerfile
  15. 2 5
      docs/en_US/extension/source.md
  16. 11 0
      docs/en_US/operation/configuration_file.md
  17. 7 0
      docs/en_US/restapi/overview.md
  18. 131 0
      docs/en_US/restapi/rules.md
  19. 74 0
      docs/en_US/restapi/streams.md
  20. 3 0
      docs/en_US/rules/overview.md
  21. 11 0
      docs/zh_CN/operation/configuration_file.md
  22. 3 0
      docs/zh_CN/rules/overview.md
  23. 4 1
      etc/kuiper.yaml
  24. 2 2
      examples/testExtension.go
  25. 136 0
      fvt_scripts/README.md
  26. 1031 0
      fvt_scripts/change_rule_status.jmx
  27. 707 0
      fvt_scripts/change_stream_rule.jmx
  28. 3 0
      fvt_scripts/change_stream_rule.txt
  29. 10 0
      fvt_scripts/iot_data.txt
  30. 二进制
      fvt_scripts/resources/jmeter_variables.png
  31. 16 0
      fvt_scripts/rule1.txt
  32. 676 0
      fvt_scripts/rule_test.jmx
  33. 48 0
      fvt_scripts/run_jmeter.sh
  34. 515 0
      fvt_scripts/select_aggr_rule.jmx
  35. 520 0
      fvt_scripts/select_aggr_rule_order.jmx
  36. 508 0
      fvt_scripts/select_all_rule.jmx
  37. 6 0
      fvt_scripts/select_condition_iot_data.txt
  38. 509 0
      fvt_scripts/select_condition_rule.jmx
  39. 26 0
      fvt_scripts/setup_env.sh
  40. 15 0
      fvt_scripts/start_kuiper.sh
  41. 663 0
      fvt_scripts/streams_test.jmx
  42. 2 1
      go.mod
  43. 3 3
      plugins/functions/countPlusOne.go
  44. 2 2
      plugins/functions/echo.go
  45. 13 13
      plugins/sinks/file.go
  46. 3 4
      plugins/sinks/memory.go
  47. 12 13
      plugins/sinks/zmq.go
  48. 9 11
      plugins/sources/random.go
  49. 36 38
      plugins/sources/zmq.go
  50. 101 104
      xsql/ast.go
  51. 14 14
      xsql/ast_agg_stmt_test.go
  52. 1 1
      xsql/expression_evaluator.go
  53. 33 33
      xsql/funcs_aggregate.go
  54. 28 29
      xsql/funcs_ast_validator.go
  55. 101 105
      xsql/funcs_ast_validator_test.go
  56. 1 1
      xsql/funcs_math.go
  57. 3 5
      xsql/funcs_misc.go
  58. 15 16
      xsql/funcs_str.go
  59. 10 10
      xsql/functions.go
  60. 23 21
      xsql/lexical.go
  61. 4 3
      xsql/metadata_util.go
  62. 15 17
      xsql/parser.go
  63. 337 351
      xsql/parser_test.go
  64. 6 7
      xsql/plans/aggregate_operator.go
  65. 61 62
      xsql/plans/aggregate_test.go
  66. 4 4
      xsql/plans/filter_operator.go
  67. 51 52
      xsql/plans/filter_test.go
  68. 1 1
      xsql/plans/having_operator.go
  69. 35 37
      xsql/plans/having_test.go
  70. 118 118
      xsql/plans/join_multi_test.go
  71. 10 11
      xsql/plans/join_operator.go
  72. 457 478
      xsql/plans/join_test.go
  73. 8 9
      xsql/plans/math_func_test.go
  74. 30 31
      xsql/plans/misc_func_test.go
  75. 3 3
      xsql/plans/order_operator.go
  76. 92 92
      xsql/plans/order_test.go
  77. 83 83
      xsql/plans/preprocessor.go
  78. 62 62
      xsql/plans/preprocessor_test.go
  79. 18 19
      xsql/plans/project_operator.go
  80. 264 275
      xsql/plans/project_test.go
  81. 82 82
      xsql/plans/str_func_test.go
  82. 17 17
      xsql/processors/extension_test.go
  83. 121 72
      xsql/processors/xsql_processor.go
  84. 188 194
      xsql/processors/xsql_processor_test.go
  85. 7 7
      xsql/util.go
  86. 2 3
      xsql/xsql_manager.go
  87. 8 9
      xsql/xsql_parser_tree_test.go
  88. 52 53
      xsql/xsql_stream_test.go
  89. 25 4
      xstream/api/stream.go
  90. 48 50
      xstream/cli/main.go
  91. 4 4
      xstream/collectors/func.go
  92. 1 2
      xstream/contexts/default.go
  93. 2 3
      xstream/demo/func_visitor.go
  94. 15 10
      xstream/extensions/mqtt_source.go
  95. 1 1
      xstream/funcs.go
  96. 6 8
      xstream/nodes/common_func.go
  97. 16 14
      common/utils/dynamic_channel_buffer.go
  98. 92 0
      xstream/nodes/prometheus.go
  99. 200 0
      xstream/nodes/sink_cache.go
  100. 0 0
      xstream/nodes/sink_node.go

+ 31 - 1
.github/workflows/release.yaml

@@ -61,11 +61,41 @@ jobs:
           with:
             name: packages-mac
             path: _packages/.
+
+    build-docker-images:
+        runs-on: ubuntu-latest
         
+        steps:
+        - uses: actions/checkout@v2
+        - name: install docker
+          run: |
+            sudo apt-get remove docker docker-engine docker.io containerd runc
+            sudo apt-get update
+            sudo apt-get install apt-transport-https ca-certificates curl gnupg-agent software-properties-common
+            curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -
+            sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable"
+            sudo apt-get update
+            sudo apt-get install docker-ce docker-ce-cli containerd.io
+        - name: prepare docker
+          run: |
+            mkdir -p $HOME/.docker
+            echo '{ "experimental": "enabled" }' | tee $HOME/.docker/config.json
+            echo '{ "experimental": true, "storage-driver": "overlay2", "max-concurrent-downloads": 50, "max-concurrent-uploads": 50 }' | sudo tee /etc/docker/daemon.json
+            sudo systemctl restart docker
+            docker version
+            docker buildx create --use --name mybuild
+        - name: build docker image
+          run: make docker
+        - name: build docker images
+          if: github.event_name == 'release'
+          run: |
+            echo ${{ secrets.DockerHubPassword }} | docker login -u ${{ secrets.DockerHubUser }} --password-stdin
+            make cross_docker
+      
     release:
         runs-on: ubuntu-latest
 
-        needs: [build, build-on-mac]
+        needs: [build, build-on-mac, build-docker-images]
 
         steps:
         - uses: actions/checkout@v2

+ 61 - 0
.github/workflows/fvt_tests.yaml

@@ -0,0 +1,61 @@
+name: Run fvt tests
+
+on:
+    push:
+    pull_request:
+    release:
+        types:
+            - published
+            - prereleased
+
+jobs:
+    fvt_tests:
+        runs-on: ubuntu-latest
+
+        steps:
+        - uses: actions/setup-go@v1
+          with:
+            go-version: '1.11.5'
+        - uses: actions/setup-java@v1
+          with:
+            java-version: '8' # The JDK version to make available on the path.
+            java-package: jdk # (jre, jdk, or jdk+fx) - defaults to jdk
+            architecture: x64 # (x64 or x86) - defaults to x64
+        - name: set up jmeter
+          env:
+            JMETER_VERSION: 5.2.1
+          run: |
+            wget -O /tmp/apache-jmeter.tgz http://mirror.bit.edu.cn/apache//jmeter/binaries/apache-jmeter-$JMETER_VERSION.tgz
+            cd /tmp && tar -xvf apache-jmeter.tgz
+            echo "jmeter.save.saveservice.output_format=xml" >> /tmp/apache-jmeter-$JMETER_VERSION/user.properties
+            echo "jmeter.save.saveservice.response_data.on_error=true" >> /tmp/apache-jmeter-$JMETER_VERSION/user.properties
+            wget -O /tmp/apache-jmeter-$JMETER_VERSION/lib/ext/mqtt-xmeter-1.13-jar-with-dependencies.jar https://github.com/emqx/mqtt-jmeter/raw/master/Download/v1.13.0/mqtt-xmeter-1.13-jar-with-dependencies.jar
+            ln -s /tmp/apache-jmeter-$JMETER_VERSION /opt/jmeter
+        - name: install emqx
+          env:
+            EMQX_VERSION: v4.0.2
+          run: |
+            wget -O emqx.deb https://www.emqx.io/downloads/broker/v4.0.2/emqx-ubuntu18.04-${EMQX_VERSION}_amd64.deb
+            sudo dpkg -i emqx.deb
+        - uses: actions/checkout@v2
+        - name: build kuiper
+          run: make
+        - name: run emqx and kuiper
+          run: sudo ./fvt_scripts/setup_env.sh
+        - name: run fvt tests
+          timeout-minutes: 5
+          run: ./fvt_scripts/run_jmeter.sh
+        - uses: actions/upload-artifact@v1
+          with:
+            name: jmeter.logs
+            path: ./jmeter_logs
+        - name: checkout out
+          run: |
+            sudo apt update && sudo apt install -y libxml2-utils
+            cd jmeter_logs
+            if [ "$(xmllint --format --xpath '/testResults/sample/@rc' $(ls *.jtl) | sed -r 's/ /\n/g;' | sort -u | grep -E 'rc=\"[45][0-9][0-9]\"|rc=\"\"')" != "" ]; then
+                echo -e "---------------------------------------------\n"
+                echo "FVT tests error"
+                exit 1
+            fi
+            

+ 2 - 1
.gitignore

@@ -24,4 +24,5 @@ node_modules/
 
 go.sum
 _build
-_packages
+_packages
+jmeter_logs

文件差异内容过多而无法显示
+ 66 - 1
README-CN.md


文件差异内容过多而无法显示
+ 66 - 1
README.md


+ 1 - 156
common/data.go

@@ -1,160 +1,5 @@
 package common
 
-import (
-	"errors"
-	"time"
-)
-
 type Rule struct {
 	Name, Json string
-}
-
-/**** Timer Mock *******/
-
-/** Ticker **/
-type Ticker interface {
-	GetC() <-chan time.Time
-	Stop()
-	Trigger(ti int64)
-}
-
-type DefaultTicker struct{
-	time.Ticker
-}
-
-func NewDefaultTicker(d int) *DefaultTicker{
-	return &DefaultTicker{*(time.NewTicker(time.Duration(d) * time.Millisecond))}
-}
-
-func (t *DefaultTicker) GetC() <-chan time.Time{
-	return t.C
-}
-
-func (t *DefaultTicker) Trigger(ti int64) {
-	Log.Fatal("ticker trigger unsupported")
-}
-
-type MockTicker struct {
-	c chan time.Time
-	duration int64
-	lastTick int64
-}
-
-func NewMockTicker(d int) *MockTicker{
-	if d <= 0 {
-		panic(errors.New("non-positive interval for MockTicker"))
-	}
-	c := make(chan time.Time, 1)
-	t := &MockTicker{
-		c: c,
-		duration: int64(d),
-		lastTick: GetMockNow(),
-	}
-	return t
-}
-
-func (t *MockTicker) SetDuration(d int){
-	t.duration = int64(d)
-	t.lastTick = GetMockNow()
-}
-
-func (t *MockTicker) GetC() <-chan time.Time{
-	return t.c
-}
-
-func (t *MockTicker) Stop() {
-	//do nothing
-}
-
-func (t *MockTicker) Trigger(ti int64) {
-	t.lastTick = ti
-	t.c <- time.Unix(ti/1000, ti%1000*1e6)
-}
-
-func (t *MockTicker) DoTick(c int64) {
-	Log.Debugf("do tick at %d, last tick %d", c, t.lastTick)
-	if t.lastTick == 0 {
-		t.lastTick = c
-	}
-	if c >= (t.lastTick + t.duration){
-		Log.Debugf("trigger tick")
-		t.Trigger(t.lastTick + t.duration)
-	}
-}
-
-/** Timer **/
-type Timer interface {
-	GetC() <-chan time.Time
-	Stop() bool
-	Reset(d time.Duration) bool
-	Trigger(ti int64)
-}
-
-type DefaultTimer struct{
-	time.Timer
-}
-
-func NewDefaultTimer(d int) *DefaultTimer{
-	return &DefaultTimer{*(time.NewTimer(time.Duration(d) * time.Millisecond))}
-}
-
-func (t *DefaultTimer) GetC() <-chan time.Time{
-	return t.C
-}
-
-func (t *DefaultTimer) Trigger(ti int64) {
-	Log.Fatal("timer trigger unsupported")
-}
-
-type MockTimer struct {
-	c chan time.Time
-	duration int64
-	createdAt int64
-}
-
-func NewMockTimer(d int) *MockTimer{
-	if d <= 0 {
-		panic(errors.New("non-positive interval for MockTimer"))
-	}
-	c := make(chan time.Time, 1)
-	t := &MockTimer{
-		c: c,
-		duration: int64(d),
-		createdAt: GetMockNow(),
-	}
-	return t
-}
-
-func (t *MockTimer) GetC() <-chan time.Time{
-	return t.c
-}
-
-func (t *MockTimer) Stop() bool{
-	t.createdAt = 0
-	return true
-}
-
-func (t *MockTimer) SetDuration(d int){
-	t.duration = int64(d)
-	t.createdAt = GetMockNow()
-	Log.Debugf("reset timer created at %v", t.createdAt)
-}
-
-func (t *MockTimer) Reset(d time.Duration) bool{
-	Log.Debugln("reset timer")
-	t.SetDuration(int(d.Nanoseconds()/1e6))
-	return true
-}
-
-func (t *MockTimer) Trigger(ti int64) {
-	t.c <- time.Unix(ti/1000, ti%1000*1e6)
-	t.createdAt = 0
-}
-
-func (t *MockTimer) DoTick(c int64) {
-	Log.Debugf("do tick at %d, created at %v", c, t.createdAt)
-	if t.createdAt > 0 && c >= (t.createdAt + t.duration){
-		Log.Info("trigger timer")
-		t.Trigger(t.createdAt + t.duration)
-	}
-}
+}

+ 189 - 0
common/kv.go

@@ -0,0 +1,189 @@
+package common
+
+import (
+	"fmt"
+	"github.com/patrickmn/go-cache"
+	"os"
+	"sync"
+)
+
+type KeyValue interface {
+	Open() error
+	Close() error
+	Set(key string, value interface{}) error
+	Replace(key string, value interface{}) error
+	Get(key string) (interface{}, bool)
+	Delete(key string) error
+	Keys() (keys []string, err error)
+}
+
+type SyncKVMap struct {
+	sync.RWMutex
+	internal map[string]*SimpleKVStore
+}
+
+func (sm *SyncKVMap) Load(path string) (result *SimpleKVStore) {
+	sm.Lock()
+	defer sm.Unlock()
+	if s, ok := sm.internal[path]; ok {
+		result = s
+	} else {
+		c := cache.New(cache.NoExpiration, 0)
+		if _, err := os.Stat(path); os.IsNotExist(err) {
+			os.MkdirAll(path, os.ModePerm)
+		}
+		result = NewSimpleKVStore(path+"/stores.data", c)
+		sm.internal[path] = result
+	}
+
+	return
+}
+
+var stores = &SyncKVMap{
+	internal: make(map[string]*SimpleKVStore),
+}
+
+func GetSimpleKVStore(path string) *SimpleKVStore {
+	return stores.Load(path)
+}
+
+type CtrlType int
+
+const (
+	OPEN CtrlType = iota
+	SAVE
+	CLOSE
+)
+
+type SimpleKVStore struct {
+	path string
+	c    *cache.Cache
+	/* These 2 channels must be mapping one by one*/
+	ctrlCh chan CtrlType
+	errCh  chan error
+}
+
+func NewSimpleKVStore(path string, c *cache.Cache) *SimpleKVStore {
+	r := &SimpleKVStore{
+		path:   path,
+		c:      c,
+		ctrlCh: make(chan CtrlType),
+		errCh:  make(chan error),
+	}
+	go r.run()
+	return r
+}
+
+func (m *SimpleKVStore) run() {
+	count := 0
+	opened := false
+	for c := range m.ctrlCh {
+		switch c {
+		case OPEN:
+			count++
+			if !opened {
+				if _, err := os.Stat(m.path); os.IsNotExist(err) {
+					m.errCh <- nil
+					break
+				}
+				if e := m.c.LoadFile(m.path); e != nil {
+					m.errCh <- e
+					break
+				}
+			}
+			m.errCh <- nil
+			opened = true
+		case CLOSE:
+			count--
+			if count == 0 {
+				opened = false
+				err := m.doClose()
+				if err != nil {
+					Log.Error(err)
+					m.errCh <- err
+					break
+				}
+			}
+			m.errCh <- nil
+		case SAVE:
+			//swallow duplicate requests
+			if len(m.ctrlCh) > 0 {
+				m.errCh <- nil
+				break
+			}
+			if e := m.c.SaveFile(m.path); e != nil {
+				Log.Error(e)
+				m.errCh <- e
+				break
+			}
+			m.errCh <- nil
+		}
+	}
+}
+
+func (m *SimpleKVStore) Open() error {
+	m.ctrlCh <- OPEN
+	return <-m.errCh
+}
+
+func (m *SimpleKVStore) Close() error {
+	m.ctrlCh <- CLOSE
+	return <-m.errCh
+}
+
+func (m *SimpleKVStore) doClose() error {
+	//e := m.c.SaveFile(m.path)
+	m.c.Flush() //Delete all of the values from memory.
+	return nil
+}
+
+func (m *SimpleKVStore) saveToFile() error {
+	m.ctrlCh <- SAVE
+	return <-m.errCh
+}
+
+func (m *SimpleKVStore) Set(key string, value interface{}) error {
+	if m.c == nil {
+		return fmt.Errorf("cache %s has not been initialized yet", m.path)
+	}
+	if err := m.c.Add(key, value, cache.NoExpiration); err != nil {
+		return err
+	}
+	return m.saveToFile()
+}
+
+func (m *SimpleKVStore) Replace(key string, value interface{}) error {
+	if m.c == nil {
+		return fmt.Errorf("cache %s has not been initialized yet", m.path)
+	}
+	m.c.Set(key, value, cache.NoExpiration)
+	return m.saveToFile()
+}
+
+func (m *SimpleKVStore) Get(key string) (interface{}, bool) {
+	return m.c.Get(key)
+}
+
+func (m *SimpleKVStore) Delete(key string) error {
+	if m.c == nil {
+		return fmt.Errorf("cache %s has not been initialized yet", m.path)
+	}
+	if _, found := m.c.Get(key); found {
+		m.c.Delete(key)
+	} else {
+		return fmt.Errorf("%s is not found", key)
+	}
+	return m.saveToFile()
+}
+
+func (m *SimpleKVStore) Keys() (keys []string, err error) {
+	if m.c == nil {
+		return nil, fmt.Errorf("Cache %s has not been initialized yet.", m.path)
+	}
+	its := m.c.Items()
+	keys = make([]string, 0, len(its))
+	for k := range its {
+		keys = append(keys, k)
+	}
+	return keys, nil
+}

+ 2 - 3
common/plugin_manager/manager.go

@@ -10,7 +10,7 @@ import (
 
 var registry map[string]plugin.Symbol
 
-func init(){
+func init() {
 	registry = make(map[string]plugin.Symbol)
 }
 
@@ -24,7 +24,7 @@ func GetPlugin(t string, ptype string) (plugin.Symbol, error) {
 		if err != nil {
 			return nil, fmt.Errorf("cannot find the plugins folder")
 		}
-		mod := path.Join(loc, ptype, t +".so")
+		mod := path.Join(loc, ptype, t+".so")
 		plug, err := plugin.Open(mod)
 		if err != nil {
 			return nil, fmt.Errorf("cannot open %s: %v", mod, err)
@@ -43,4 +43,3 @@ func ucFirst(str string) string {
 	}
 	return ""
 }
-

+ 4 - 4
common/templates/funcs.go

@@ -5,10 +5,10 @@ import (
 )
 
 //Use the name json in func map
-func JsonMarshal(v interface {}) (string, error) {
-	if a, err := json.Marshal(v); err != nil{
+func JsonMarshal(v interface{}) (string, error) {
+	if a, err := json.Marshal(v); err != nil {
 		return "", err
-	}else{
+	} else {
 		return string(a), nil
 	}
-}
+}

+ 38 - 24
common/time_util.go

@@ -2,6 +2,7 @@ package common
 
 import (
 	"fmt"
+	"github.com/benbjohnson/clock"
 	"time"
 )
 
@@ -66,14 +67,14 @@ func InterfaceToUnixMilli(i interface{}, format string) (int64, error) {
 		var ti time.Time
 		var err error
 		var f = JSISO
-		if format != ""{
+		if format != "" {
 			f, err = convertFormat(format)
-			if err != nil{
+			if err != nil {
 				return 0, err
 			}
 		}
 		ti, err = time.Parse(f, t)
-		if err != nil{
+		if err != nil {
 			return 0, err
 		}
 		return TimeToUnixMilli(ti), nil
@@ -96,14 +97,14 @@ func InterfaceToTime(i interface{}, format string) (time.Time, error) {
 		var ti time.Time
 		var err error
 		var f = JSISO
-		if format != ""{
+		if format != "" {
 			f, err = convertFormat(format)
-			if err != nil{
+			if err != nil {
 				return ti, err
 			}
 		}
 		ti, err = time.Parse(f, t)
-		if err != nil{
+		if err != nil {
 			return ti, err
 		}
 		return ti, nil
@@ -113,21 +114,21 @@ func InterfaceToTime(i interface{}, format string) (time.Time, error) {
 }
 
 func TimeFromUnixMilli(t int64) time.Time {
-	return time.Unix(t/1000, t%1000).UTC()
+	return time.Unix(t/1000, (t%1000)*1e6).UTC()
 }
 
-func ParseTime(t string, f string) (time.Time, error){
-	if f, err := convertFormat(f); err != nil{
+func ParseTime(t string, f string) (time.Time, error) {
+	if f, err := convertFormat(f); err != nil {
 		return time.Now(), err
-	}else{
+	} else {
 		return time.Parse(f, t)
 	}
 }
 
-func FormatTime(time time.Time, f string) (string, error){
-	if f, err := convertFormat(f); err != nil{
+func FormatTime(time time.Time, f string) (string, error) {
+	if f, err := convertFormat(f); err != nil {
 		return "", err
-	}else{
+	} else {
 		return time.Format(f), nil
 	}
 }
@@ -144,7 +145,7 @@ func FormatTime(time time.Time, f string) (string, error){
 //	return f
 //}
 
-func convertFormat(f string) (string, error){
+func convertFormat(f string) (string, error) {
 	formatRune := []rune(f)
 	lenFormat := len(formatRune)
 	out := ""
@@ -152,7 +153,7 @@ func convertFormat(f string) (string, error){
 		switch r := formatRune[i]; r {
 		case 'Y', 'y':
 			j := 1
-			for ; i+j < lenFormat && j<=4; j++ {
+			for ; i+j < lenFormat && j <= 4; j++ {
 				if formatRune[i+j] != r {
 					break
 				}
@@ -170,7 +171,7 @@ func convertFormat(f string) (string, error){
 			out += "AD"
 		case 'M': // M MM MMM MMMM month of year
 			j := 1
-			for ; i+j < lenFormat && j<=4; j++ {
+			for ; i+j < lenFormat && j <= 4; j++ {
 				if formatRune[i+j] != r {
 					break
 				}
@@ -188,7 +189,7 @@ func convertFormat(f string) (string, error){
 			}
 		case 'd': // d dd day of month
 			j := 1
-			for ; i+j < lenFormat && j<=2; j++ {
+			for ; i+j < lenFormat && j <= 2; j++ {
 				if formatRune[i+j] != r {
 					break
 				}
@@ -203,7 +204,7 @@ func convertFormat(f string) (string, error){
 			}
 		case 'E': // M MM MMM MMMM month of year
 			j := 1
-			for ; i+j < lenFormat && j<=4; j++ {
+			for ; i+j < lenFormat && j <= 4; j++ {
 				if formatRune[i+j] != r {
 					break
 				}
@@ -219,7 +220,7 @@ func convertFormat(f string) (string, error){
 			}
 		case 'H': // HH
 			j := 1
-			for ; i+j < lenFormat && j<=2; j++ {
+			for ; i+j < lenFormat && j <= 2; j++ {
 				if formatRune[i+j] != r {
 					break
 				}
@@ -234,14 +235,14 @@ func convertFormat(f string) (string, error){
 			}
 		case 'h': // h hh
 			j := 1
-			for ; i+j < lenFormat && j<=2; j++ {
+			for ; i+j < lenFormat && j <= 2; j++ {
 				if formatRune[i+j] != r {
 					break
 				}
 			}
 			i = i + j - 1
 			switch j {
-			case 1:  // h
+			case 1: // h
 				out += "3"
 			case 2: // hh
 				out += "03"
@@ -265,7 +266,7 @@ func convertFormat(f string) (string, error){
 			}
 		case 's': // s ss
 			j := 1
-			for ; i+j < lenFormat && j<=2 ; j++ {
+			for ; i+j < lenFormat && j <= 2; j++ {
 				if formatRune[i+j] != r {
 					break
 				}
@@ -281,7 +282,7 @@ func convertFormat(f string) (string, error){
 
 		case 'S': // S SS SSS
 			j := 1
-			for ; i+j < lenFormat && j<=3; j++ {
+			for ; i+j < lenFormat && j <= 3; j++ {
 				if formatRune[i+j] != r {
 					break
 				}
@@ -301,7 +302,7 @@ func convertFormat(f string) (string, error){
 			out += "-0700"
 		case 'X': // X XX XXX
 			j := 1
-			for ; i+j < lenFormat && j<=3; j++ {
+			for ; i+j < lenFormat && j <= 3; j++ {
 				if formatRune[i+j] != r {
 					break
 				}
@@ -340,4 +341,17 @@ func convertFormat(f string) (string, error){
 		}
 	}
 	return out, nil
+}
+
+//Time related. For Mock
+func GetTicker(duration int) *clock.Ticker {
+	return Clock.Ticker(time.Duration(duration) * time.Millisecond)
+}
+
+func GetTimer(duration int) *clock.Timer {
+	return Clock.Timer(time.Duration(duration) * time.Millisecond)
+}
+
+func GetNowInMilli() int64 {
+	return TimeToUnixMilli(Clock.Now())
 }

+ 35 - 0
common/time_util_test.go

@@ -0,0 +1,35 @@
+package common
+
+import (
+	"testing"
+	"time"
+)
+
+func TestDateToAndFromMilli(t *testing.T) {
+	var tests = []struct {
+		m int64
+		t time.Time
+	}{
+		{int64(1579140864913), time.Date(2020, time.January, 16, 2, 14, 24, 913000000, time.UTC)},
+		{int64(4913), time.Date(1970, time.January, 1, 0, 0, 4, 913000000, time.UTC)},
+		{int64(2579140864913), time.Date(2051, time.September, 24, 4, 1, 4, 913000000, time.UTC)},
+		{int64(-1579140864913), time.Date(1919, time.December, 17, 21, 45, 35, 87000000, time.UTC)},
+	}
+	for i, tt := range tests{
+		time := TimeFromUnixMilli(tt.m)
+		if !time.Equal(tt.t){
+			t.Errorf("%d time from milli result mismatch:\n\nexp=%#v\n\ngot=%#v\n\n", i, tt.t, time)
+		}
+		milli := TimeToUnixMilli(tt.t)
+		if tt.m != milli{
+			t.Errorf("%d time to milli result mismatch:\n\nexp=%#v\n\ngot=%#v\n\n", i, tt.m, milli)
+		}
+	}
+}
+
+func TestMockClock(t *testing.T) {
+	n := GetNowInMilli()
+	if n != 0{
+		t.Errorf("mock clock now mismatch:\n\nexp=%#v\n\ngot=%#v\n\n", 0, n)
+	}
+}

+ 48 - 199
common/util.go

@@ -4,53 +4,34 @@ import (
 	"bytes"
 	"encoding/json"
 	"fmt"
+	"github.com/benbjohnson/clock"
 	"github.com/go-yaml/yaml"
-	"github.com/patrickmn/go-cache"
 	"github.com/sirupsen/logrus"
 	"io/ioutil"
 	"os"
 	"path"
 	"path/filepath"
-	"time"
+	"sort"
+	"strings"
 )
 
 const (
-	logFileName = "stream.log"
-	etc_dir = "/etc/"
-	data_dir = "/data/"
-	log_dir = "/log/"
+	logFileName   = "stream.log"
+	etc_dir       = "/etc/"
+	data_dir      = "/data/"
+	log_dir       = "/log/"
+	StreamConf    = "kuiper.yaml"
+	KuiperBaseKey = "KuiperBaseKey"
 )
 
 var (
-	Log *logrus.Logger
-	Config *XStreamConf
+	Log       *logrus.Logger
+	Config    *XStreamConf
 	IsTesting bool
-	logFile *os.File
-	mockTicker *MockTicker
-	mockTimer *MockTimer
-	mockNow int64
+	Clock     clock.Clock
+	logFile   *os.File
 )
 
-type logRedirect struct {
-
-}
-
-func (l *logRedirect) Errorf(f string, v ...interface{}) {
-	Log.Error(fmt.Sprintf(f, v...))
-}
-
-func (l *logRedirect) Infof(f string, v ...interface{}) {
-	Log.Info(fmt.Sprintf(f, v...))
-}
-
-func (l *logRedirect) Warningf(f string, v ...interface{}) {
-	Log.Warning(fmt.Sprintf(f, v...))
-}
-
-func (l *logRedirect) Debugf(f string, v ...interface{}) {
-	Log.Debug(fmt.Sprintf(f, v...))
-}
-
 func LoadConf(confName string) ([]byte, error) {
 	confDir, err := GetConfLoc()
 	if err != nil {
@@ -66,18 +47,32 @@ func LoadConf(confName string) ([]byte, error) {
 }
 
 type XStreamConf struct {
-	Debug bool `yaml:"debug"`
-	Port int `yaml:"port"`
+	Debug          bool `yaml:"debug"`
+	Port           int  `yaml:"port"`
+	RestPort       int  `yaml:"restPort"`
+	Prometheus     bool `yaml:"prometheus"`
+	PrometheusPort int  `yaml:"prometheusPort"`
 }
 
-var StreamConf = "kuiper.yaml"
-const KuiperBaseKey = "KuiperBaseKey"
-func init(){
+func init() {
 	Log = logrus.New()
 	Log.SetFormatter(&logrus.TextFormatter{
 		DisableColors: true,
 		FullTimestamp: true,
 	})
+	Log.Debugf("init with args %s", os.Args)
+	for _, arg := range os.Args {
+		if strings.HasPrefix(arg, "-test.") {
+			IsTesting = true
+			break
+		}
+	}
+	if IsTesting {
+		Log.Debugf("running in testing mode")
+		Clock = clock.NewMock()
+	} else {
+		Clock = clock.New()
+	}
 }
 
 func InitConf() {
@@ -90,7 +85,7 @@ func InitConf() {
 		Log.Fatal(err)
 	}
 
-	if c, ok := cfg["basic"]; !ok{
+	if c, ok := cfg["basic"]; !ok {
 		Log.Fatal("No basic config in kuiper.yaml")
 	} else {
 		Config = &c
@@ -113,112 +108,24 @@ func InitConf() {
 	}
 }
 
-type KeyValue interface {
-	Open() error
-	Close() error
-	Set(key string, value interface{}) error
-	Get(key string) (interface{}, bool)
-	Delete(key string) error
-	Keys() (keys []string, err error)
-}
-
-type SimpleKVStore struct {
-	path string
-	c *cache.Cache;
-}
-
-
-var stores = make(map[string]*SimpleKVStore)
-
-func GetSimpleKVStore(path string) *SimpleKVStore {
-	if s, ok := stores[path]; ok {
-		return s
-	} else {
-		c := cache.New(cache.NoExpiration, 0)
-		if _, err := os.Stat(path); os.IsNotExist(err) {
-			os.MkdirAll(path, os.ModePerm)
-		}
-		sStore := &SimpleKVStore{path: path + "/stores.data", c: c}
-		stores[path] = sStore
-		return sStore
-	}
-}
-
-func (m *SimpleKVStore) Open() error  {
-	if _, err := os.Stat(m.path); os.IsNotExist(err) {
-		return nil
-	}
-	if e := m.c.LoadFile(m.path); e != nil {
-		return e
-	}
-	return nil
-}
-
-func (m *SimpleKVStore) Close() error  {
-	e := m.saveToFile()
-	m.c.Flush() //Delete all of the values from memory.
-	return e
-}
-
-func (m *SimpleKVStore) saveToFile() error {
-	if e := m.c.SaveFile(m.path); e != nil {
-		return e
-	}
-	return nil
-}
-
-func (m *SimpleKVStore) Set(key string, value interface{}) error  {
-	if m.c == nil {
-		return fmt.Errorf("cache %s has not been initialized yet", m.path)
-	}
-	if err := m.c.Add(key, value, cache.NoExpiration); err != nil {
-		return err
-	}
-	return m.saveToFile()
-}
-
-func (m *SimpleKVStore) Get(key string) (interface{}, bool)  {
-	return m.c.Get(key)
-}
-
-func (m *SimpleKVStore) Delete(key string) error {
-	if m.c == nil {
-		return fmt.Errorf("cache %s has not been initialized yet", m.path)
-	}
-	if _, found := m.c.Get(key); found {
-		m.c.Delete(key)
-	}else{
-		return fmt.Errorf("%s is not found", key)
-	}
-	return m.saveToFile()
-}
-
-func (m *SimpleKVStore) Keys() (keys []string, err error) {
-	if m.c == nil {
-		return nil, fmt.Errorf("Cache %s has not been initialized yet.", m.path)
-	}
-	its := m.c.Items()
-	keys = make([]string, 0, len(its))
-	for k := range its {
-		keys = append(keys, k)
-	}
-	return keys, nil
-}
-
 func PrintMap(m map[string]string, buff *bytes.Buffer) {
-
-	for k, v := range m {
-		buff.WriteString(fmt.Sprintf("%s: %s\n", k, v))
+	si := make([]string, 0, len(m))
+	for s := range m {
+		si = append(si, s)
+	}
+	sort.Strings(si)
+	for _, s := range si {
+		buff.WriteString(fmt.Sprintf("%s: %s\n", s, m[s]))
 	}
 }
 
-func CloseLogger(){
+func CloseLogger() {
 	if logFile != nil {
 		logFile.Close()
 	}
 }
 
-func GetConfLoc()(string, error){
+func GetConfLoc() (string, error) {
 	return GetLoc(etc_dir)
 }
 
@@ -226,7 +133,7 @@ func GetDataLoc() (string, error) {
 	return GetLoc(data_dir)
 }
 
-func GetLoc(subdir string)(string, error) {
+func GetLoc(subdir string) (string, error) {
 	dir, err := os.Getwd()
 	if err != nil {
 		return "", err
@@ -277,81 +184,23 @@ func GetAndCreateDataLoc(dir string) (string, error) {
 	return d, nil
 }
 
-//Time related. For Mock
-func GetTicker(duration int) Ticker {
-	if IsTesting{
-		if mockTicker == nil{
-			mockTicker = NewMockTicker(duration)
-		}else{
-			mockTicker.SetDuration(duration)
-		}
-		return mockTicker
-	}else{
-		return NewDefaultTicker(duration)
-	}
-}
-
-func GetTimer(duration int) Timer {
-	if IsTesting{
-		if mockTimer == nil{
-			mockTimer = NewMockTimer(duration)
-		}else{
-			mockTimer.SetDuration(duration)
-		}
-		return mockTimer
-	}else{
-		return NewDefaultTimer(duration)
-	}
-}
-
-func GetNowInMilli() int64{
-	if IsTesting {
-		return GetMockNow()
-	}else{
-		return TimeToUnixMilli(time.Now())
-	}
-}
-
 func ProcessPath(p string) (string, error) {
 	if abs, err := filepath.Abs(p); err != nil {
 		return "", nil
 	} else {
 		if _, err := os.Stat(abs); os.IsNotExist(err) {
-			return "", err;
+			return "", err
 		}
 		return abs, nil
 	}
 }
 
-/****** For Test Only ********/
-func GetMockTicker() *MockTicker{
-	return mockTicker
-}
-
-func ResetMockTicker(){
-	if mockTicker != nil{
-		mockTicker.lastTick = 0
-	}
-}
-
-func GetMockTimer() *MockTimer{
-	return mockTimer
-}
-
-func SetMockNow(now int64){
-	mockNow = now
-}
-
-func GetMockNow() int64{
-	return mockNow
-}
-
 /*********** Type Cast Utilities *****/
 //TODO datetime type
-func ToString(input interface{}) string{
+func ToString(input interface{}) string {
 	return fmt.Sprintf("%v", input)
 }
-func ToInt(input interface{}) (int, error){
+func ToInt(input interface{}) (int, error) {
 	switch t := input.(type) {
 	case float64:
 		return int(t), nil
@@ -368,10 +217,10 @@ func ToInt(input interface{}) (int, error){
 *   Convert a map into a struct. The output parameter must be a pointer to a struct
 *   The struct can have the json meta data
  */
-func MapToStruct(input map[string]interface{}, output interface{}) error{
+func MapToStruct(input map[string]interface{}, output interface{}) error {
 	// convert map to json
 	jsonString, err := json.Marshal(input)
-	if err != nil{
+	if err != nil {
 		return err
 	}
 

+ 6 - 6
common/util_test.go

@@ -9,7 +9,7 @@ import (
 
 func TestSimpleKVStore_Funcs(t *testing.T) {
 	abs, _ := filepath.Abs("test.data")
-	if f, _ := os.Stat(abs); f != nil{
+	if f, _ := os.Stat(abs); f != nil {
 		_ = os.Remove(abs)
 	}
 
@@ -22,7 +22,7 @@ func TestSimpleKVStore_Funcs(t *testing.T) {
 	v, _ := ks.Get("foo")
 	reflect.DeepEqual("bar", v)
 
-	_= ks.Set("foo1", "bar1")
+	_ = ks.Set("foo1", "bar1")
 	v1, _ := ks.Get("foo1")
 	reflect.DeepEqual("bar1", v1)
 
@@ -36,9 +36,9 @@ func TestSimpleKVStore_Funcs(t *testing.T) {
 		t.Errorf("Failed to close data: %s.", e2)
 	}
 
-	if _, f := ks.Get("foo"); f {
-		t.Errorf("Should not find the foo key.")
-	}
+	//if _, f := ks.Get("foo"); f {
+	//	t.Errorf("Should not find the foo key.")
+	//}
 
 	_ = ks.Open()
 	if v, ok := ks.Get("foo"); ok {
@@ -56,4 +56,4 @@ func TestSimpleKVStore_Funcs(t *testing.T) {
 	}
 
 	_ = os.Remove(abs)
-}
+}

+ 1 - 1
deploy/docker/Dockerfile

@@ -4,7 +4,7 @@ COPY . /go/kuiper
 
 WORKDIR /go/kuiper
 
-RUN apk add upx gcc make git libc-dev binutils-gold && make 
+RUN apk add upx gcc make git libc-dev binutils-gold pkgconfig zeromq-dev && make
 
 FROM alpine:3.10
 

文件差异内容过多而无法显示
+ 2 - 5
docs/en_US/extension/source.md


+ 11 - 0
docs/en_US/operation/configuration_file.md

@@ -9,3 +9,14 @@ basic:
   debug: false
 ```
 
+## Prometheus Configuration
+
+Kuiper can export metrics to prometheus if ``prometheus`` option is true. The prometheus will be served with the port specified by ``prometheusPort`` option.
+
+```yaml
+basic:
+  prometheus: true
+  prometheusPort: 20499
+```
+For such a default configuration, Kuiper will export metrics and serve prometheus at ``http://localhost:20499/metrics``
+

+ 7 - 0
docs/en_US/restapi/overview.md

@@ -0,0 +1,7 @@
+Kuiper provides a set of REST API for streams and rules management in addition to CLI. 
+
+By default, the REST API are running in port 8080. You can change the port in `/etc/kuiper.yaml` for the `restPort` property.
+
+- [Streams](streams.md)
+- [Rules](rules.md)
+

+ 131 - 0
docs/en_US/restapi/rules.md

@@ -0,0 +1,131 @@
+# Rules management
+
+The Kuiper REST api for rules allows you to manage rules, such as create, show, drop, describe, start, stop and restart rules. 
+
+## create a rule
+
+The API accepts a JSON content and create and start a rule.
+```shell
+POST http://localhost:9081/rules
+```
+Request Sample
+
+```json
+{
+  "id": "rule1",
+  "sql": "SELECT * FROM demo",
+  "actions": [{
+    "log":  {}
+  }]
+}
+```
+
+
+## show rules
+
+The API is used for displaying all of rules defined in the server.
+
+```shell
+GET http://localhost:9081/rules
+```
+
+Response Sample:
+
+```json
+["rule1","rule2"]
+```
+
+## describe a rule
+
+The API is used for print the detailed definition of rule.
+
+```shell
+GET http://localhost:9081/rules/{id}
+```
+
+Path parameter `id` is the id or name of the rule.
+
+Response Sample: 
+
+```json
+{
+  "sql": "SELECT * from demo",
+  "actions": [
+    {
+      "log": {}
+    },
+    {
+      "mqtt": {
+        "server": "tcp://127.0.0.1:1883",
+        "topic": "demoSink"
+      }
+    }
+  ]
+}
+```
+
+## drop a rule
+
+The API is used for drop the rule.
+
+```shell
+DELETE http://localhost:8080/rules/{id}
+```
+
+
+## start a rule
+
+The API is used to start running the rule.
+
+```shell
+POST http://localhost:8080/rules/{id}/start
+```
+
+
+## stop a rule
+
+The API is used to stop running the rule.
+
+```shell
+POST http://localhost:8080/rules/{id}/stop
+```
+
+## restart a rule
+
+The API is used to restart the rule.
+
+```shell
+POST http://localhost:8080/rules/{id}/restart
+```
+
+## get the status of a rule
+
+The command is used to get the status of the rule. If the rule is running, the metrics will be retrieved realtime. The status can be
+- running with metrics: $metrics
+- stopped: $reason
+
+```shell
+GET http://localhost:8080/rules/{id}/status
+```
+
+Response Sample:
+
+```shell
+running with metrics:
+{
+    "source_demo_0_records_in_total":5,
+    "source_demo_0_records_out_total":5,
+    "source_demo_0_exceptions_total":0,
+    "source_demo_0_process_latency_ms":0,
+    "source_demo_0_buffer_length":0,
+    "source_demo_0_last_invocation":"2020-01-02T11:28:33.054821",
+    ... 
+    "op_filter_0_records_in_total":5,
+    "op_filter_0_records_out_total":2,
+    "op_filter_0_exceptions_total":0,
+    "op_filter_0_process_latency_ms":0,
+    "op_filter_0_buffer_length":0,
+    "op_filter_0_last_invocation":"2020-01-02T11:28:33.054821",
+    ...
+}
+```

+ 74 - 0
docs/en_US/restapi/streams.md

@@ -0,0 +1,74 @@
+# Streams management
+
+The Kuiper REST api for streams allows you to manage the streams, such as create, describe, show and drop stream definitions.
+
+## create a stream
+
+The API is used for creating a stream. For more detailed information of stream definition, please refer to [streams](../sqls/streams.md).
+
+```shell
+POST http://localhost:9081/streams
+```
+Request sample, the request is a json string with `sql` field.
+
+```json
+{"sql":"create stream my_stream (id bigint, name string, score float) WITH ( datasource = \"topic/temperature\", FORMAT = \"json\", KEY = \"id\")"}
+```
+
+This API can run any stream sql statements, not only stream creation.
+
+## show streams
+
+The API is used for displaying all of streams defined in the server.
+
+```shell
+GET http://localhost:9081/streams
+```
+
+Response Sample:
+
+```json
+["mystream"]
+```
+
+## describe a stream
+
+The API is used for print the detailed definition of stream.
+
+```shell
+GET http://localhost:9081/streams/{id}}
+```
+
+Response Sample:
+
+```shell
+{
+  "Name": "demo",
+  "StreamFields": [
+    {
+      "Name": "temperature",
+      "FieldType": {
+        "Type": 2
+      }
+    },
+    {
+      "Name": "ts",
+      "FieldType": {
+        "Type": 1
+      }
+    }
+  ],
+  "Options": {
+    "DATASOURCE": "demo",
+    "FORMAT": "JSON"
+  }
+}
+```
+
+## drop a stream
+
+The API is used for drop the stream definition.
+
+```shell
+DELETE http://localhost:9081/streams/{id}
+```

+ 3 - 0
docs/en_US/rules/overview.md

@@ -52,6 +52,9 @@ Currently, 3 kinds of actions are supported: [log](sinks/logs.md), [mqtt](sinks/
 | concurrency | int: 1   | Specify how many instances of the sink will be run. If the value is bigger than 1, the order of the messages may not be retained. |
 | bufferLength | int: 1024   | Specify how many messages can be buffered in memory. If the buffered messages exceed the limit, the sink will block message receiving until the buffered messages have been sent out so that the buffered size is less than the limit. |
 | runAsync        | bool:false   | Whether the sink will run asynchronously for better performance. If it is true, the sink result order is not promised.  |
+| retryInterval   | int:1000   | Specify how many milliseconds will the sink retry to send data out if the previous send failed  |
+| cacheLength     | int:10240   | Specify how many messages can be cached. The cached messages will be resent to external system until the data sent out successfully. The cached message will be sent in order except in runAsync or concurrent mode. The cached message will be saved to disk in fixed intervals.  |
+| cacheSaveInterval  | int:1000   | Specify the interval to save cached message to the disk. Notice that, if the rule is closed in plan, all the cached messages will be saved at close. A larger value can reduce the saving overhead but may lose more cache messages when the system is interrupted in error.  |
 
 Actions could be customized to support different kinds of outputs, see [extension](../extension/overview.md) for more detailed info.
 

+ 11 - 0
docs/zh_CN/operation/configuration_file.md

@@ -9,3 +9,14 @@ basic:
   debug: false
 ```
 
+## Prometheus配置
+
+如果``prometheus``参数设置为true,Kuiper 将把运行指标暴露到prometheus。Prometheus将运行在``prometheusPort``参数指定的端口上。
+
+```yaml
+basic:
+  prometheus: true
+  prometheusPort: 20499
+```
+在如上默认配置中,Kuiper暴露于Prometheusd 运行指标可通过``http://localhost:20499/metrics``访问。
+

+ 3 - 0
docs/zh_CN/rules/overview.md

@@ -52,6 +52,9 @@
 | concurrency | int: 1   | 设置运行的线程数。该参数值大于1时,消息发出的顺序可能无法保证。 |
 | bufferLength | int: 1024   | 设置可缓存消息数目。若缓存消息数超过此限制,sink将阻塞消息接收,直到缓存消息被消费使得缓存消息数目小于限制为止。|
 | runAsync        | bool:false   | 设置是否异步运行输出操作以提升性能。请注意,异步运行的情况下,输出结果顺序不能保证。  |
+| retryInterval   | int:1000   | 设置信息发送失败后重试等待时间,单位为毫秒|
+| cacheLength     | int:10240   | 设置最大消息缓存数量。缓存的消息会一直保留直到消息发送成功。缓存消息将按顺序发送,除非运行在异步或者并发模式下。缓存消息会定期存储到磁盘中。  |
+| cacheSaveInterval  | int:1000   | 设置缓存存储间隔时间,单位为毫秒。需要注意的是,当规则关闭时,缓存会自动存储。该值越大,则缓存保存开销越小,但系统意外退出时缓存丢失的风险变大。 |
 
 可以自定义动作以支持不同种类的输出,有关更多详细信息,请参见 [extension](../extension/overview.md) 。
 

+ 4 - 1
etc/kuiper.yaml

@@ -1,4 +1,7 @@
 basic:
   # true|false, with debug level, it prints more debug info
   debug: false
-  port: 20498
+  port: 20498
+  restPort: 9081
+  prometheus: false
+  prometheusPort: 20499

+ 2 - 2
examples/testExtension.go

@@ -17,10 +17,10 @@ func main() {
 	log.Infof("db location is %s", dbDir)
 
 	demo := `DROP STREAM ext`
-	processors.NewStreamProcessor(demo, path.Join(dbDir, "stream")).Exec()
+	processors.NewStreamProcessor(path.Join(dbDir, "stream")).ExecStmt(demo)
 
 	demo = "CREATE STREAM ext (count bigint) WITH (DATASOURCE=\"users\", FORMAT=\"JSON\", TYPE=\"random\")"
-	_, err = processors.NewStreamProcessor(demo, path.Join(dbDir, "stream")).Exec()
+	_, err = processors.NewStreamProcessor(path.Join(dbDir, "stream")).ExecStmt(demo)
 	if err != nil {
 		panic(err)
 	}

+ 136 - 0
fvt_scripts/README.md

@@ -0,0 +1,136 @@
+## Overview
+
+Kuiper FVT (functional verification tests)  covers following scenarios. 
+
+- Basic functions of HTTP REST-API 
+- Basic functions of CLI
+- Complex end-2-end scenario for Kuiper source, processing and sink
+
+The scenarios will be invoked automatically in Github actions with any new code commit or push request. Another Raspberry Pi continouly integration environment will also be ready for running test cases in ARM environment. So if receives any failed FVT running, please re-check the code or update the scripts if necessary.
+
+Kuiper project uses JMeter for writing the scripts for following reasons,
+
+- Easy to write testcases for HTTP REST-API & CLI
+- Capabilities of publish and subscribe MQTT message. The Kuiper end-2-end scenarios requires MQTT client for pub/sub message, and JMeter provides a unified approach for supporting such functions
+- Capabilities of writing complex content assertions. Besides some simple ways of content assertions, JMeter also provides ``BeanShell Assertion``, which can be used for extract and process complex message contents. 
+
+## Run script in local development environment
+
+**Prepare JMeter**
+
+Kuiper uses JMeter for FVT test scenarios, includes REST-API, CLI and end to end test scenarios. 
+
+- Install JRE - requires JRE 8+
+- Download and extract [JMeter](http://jmeter.apache.org/download_jmeter.cgi). 
+
+**Install MQTT broker**
+
+Because test scripts uses MQTT broker for source and sink of Kuiper rule, an MQTT broker is required for running the scripts. If you use a broker that cannot be accessed from ``tcp://127.0.0.1:1883``, you should modify the script and specify your MQTT broker address.
+
+- Modify servers to your MQTT broker address in Kuiper configuration file ``etc/mqtt_source.yaml``. 
+- Modify the script file that you want to run.
+  - ``mqtt_srv``: The default value is ``127.0.0.1``, you need to update it if you have a different broker. Refer to below screenshot, ``Test Plan > User Defined Variables > mqtt_srv``.
+    
+    ![jmeter_variables](resources/jmeter_variables.png)
+    
+  - If you run test Kuiper server at another address or port, you need also change below two config.
+    - ``Test Plan > User Defined Variables > srv``: The Kuiper server address, by default is at ``127.0.0.1``.
+    - ``Test Plan > User Defined Variables > rest_port``: The Kuiper server RestAPI port, by default is ``9081``, please change it if running Kuiper at a different port.
+
+**Run JMeter**
+
+For most of scripts, you can just start JMeter by default way, such as ``bin/jmeter.sh`` in Mac or Linux. But some of scripts need to pass some parameters before running them. Please refer to below for detailed. Please make sure you start MQTT broker & Kuiper before running the tests.
+
+## Scenarios
+
+- [Basic stream test](streams_test.jmx)
+
+  The script tests the basic steps for stream operations, include both API & CLI.
+
+  - Create/Delete/Describe/Show stream for RestAPI
+  - Create/Delete/Describe/Show stream for CLI
+
+  The script need to be told about the location of Kuiper install directory, so script knows where to invoke Kuiper CLI.
+
+  - Specify the ``base`` property in the JMeter command line, the ``base`` is where Kuiper installs. Below is command for starting JMeter.
+
+    ```shell
+    bin/jmeter.sh -Dbase="/opt/kuiper"
+    ```
+
+- [Basic rules test](rule_test.jmx)
+  
+  The script tests stream and rule operations.
+  
+  - Create a stream with MQTT source, and then call rule management
+    - Create/Delete/Describe/Show rule for RestAPI
+    - Create/Delete/Describe/Show rule for CLI
+  - Delete stream definition at the last step of test scenario
+  
+  The script need to be told about the location of Kuiper install directory, so script knows where to invoke Kuiper CLI.
+  
+  - Specify the ``base`` property in the JMeter command line, the ``base`` is where Kuiper installs. 
+  - Specify the ``fvt`` property in the JMeter command line, the ``fvt`` is where you develop Kuiper, script will read rule file  ``fvt_scripts/rule1.txt`` from the location.
+  
+  - Modify ``mqtt.server`` to your MQTT broker address in file ``fvt_scripts/rule1.txt``.
+  
+  - So below is command for starting JMeter.
+  
+    ```shell
+    bin/jmeter.sh -Dbase="/opt/kuiper" -Dfvt="/Users/rockyjin/Downloads/workspace/edge/src/kuiper"
+    ```
+  
+- [Select all (*) records rule test](select_all_rule.jmx)
+
+  The scenario tests a rule that select all of records from a stream.
+
+  - Stream source is MQTT, and JSON data are sent to an MQTT topic by JMeter. The sent data are read from file ``iot_data.txt``, where the 1st column is ``device_id``, the 2nd column is ``temperature``, the 3rd column is ``humidity``. There are totally 10 records in the file.
+  - The processing SQL is ``SELECT * FROM demo``,  so all of data will be processed and sent to sinks.
+  - There are two sinks for the rule, one is log, and another is MQTT sink. So result will be sent to those sinks.
+  - Another JMeter mock-up user subscribes MQTT result topic. JMeter validates message number and  content sent by the rule. If the record cotent is not correct then JMeter response assertion will be failed. If record number is not correct, the script will not be stopped, until CI (continuous integration) pipeline kills it with timeout settings. If you run the script in local, you'll have to stop the test manually.
+
+- [Select records with condition](select_condition_rule.jmx)
+
+  This scenario test is very similar to the last one, except the rule filters the record with a condition.
+
+  - The processing SQL is ``SELECT * FROM demo WHERE temperature > 30``, so all of the data that with temperature less than 30 will be fitered. The script read data from  file ``iot_data.txt``, totally 10 records.
+  - Another JMeter mock-up user subscribes MQTT result topic, and expected result are saved in file ``select_condition_iot_data.txt``. If the record cotent is not correct then JMeter response assertion will be failed. If record number is not correct, the script will not be stopped, until CI (continuous integration) pipeline kills it with timeout settings. If you run the script in local, you'll have to stop the test manually.
+
+- [Aggregation rule]()
+
+  The script automated steps described in [this blog](https://www.emqx.io/blog/lightweight-edge-computing-emqx-kuiper-and-aws-iot-hub-integration-solution), except for the sink target changes to local EMQ broker (not AWS IoT Hub). 
+
+  - The processing SQL is as following.
+
+    ```sql
+    SELECT avg(temperature) AS t_av, max(temperature) AS t_max, min(temperature) AS t_min, COUNT(*) As t_count, split_value(mqtt(topic), "/", 1) AS device_id FROM demo GROUP BY device_id, TUMBLINGWINDOW(ss, 5)
+    ```
+
+  - Another JMeter mock-up user subscribes MQTT result topic, and it waits for 15 seconds to get all of analysis result arrays.  With the beanshell assertion, it calculates total number of ``t_count`` for device 1 & 2. If the number is not correct, then it fails. 
+
+- [Change rule status & get rule metrics](change_rule_status.jmx)
+
+  This script creates stream and rule, then get metrics of rule, and assert message number processed in stream processing line. Additionally, script will stop, start or restart the rule, and verify the metric value of rule. 
+
+  Another JMeter mock-up user subscribes MQTT result topic, and assert message number and contents.
+
+- [Change stream definition and restart rule](change_stream_rule.jmx)
+
+  The script tests scenarios for following cases,
+
+  - Use the SQL to select a field that is not existed in stream definition, it returns ``[{}]``.
+  - stream definition changed, and rule works well after restarting it.
+
+  Another JMeter mock-up user subscribes MQTT result topic, and assert message number and contents.
+
+- [Aggregation with ORDER BY](select_aggr_rule_order.jmx)
+
+  - The script adds ``ORDER BY`` statement based on ``Aggregation rule``.
+
+    ```sql
+    SELECT temperature, humidity, split_value(mqtt(topic), "/", 1) AS device_id FROM demo GROUP BY TUMBLINGWINDOW(ss, 10) ORDER BY device_id DESC, temperature
+    ```
+
+  - Another JMeter mock-up user subscribes MQTT result topic, and assert the order for device_id field is descending, and temperature is ascending.
+
+  

文件差异内容过多而无法显示
+ 1031 - 0
fvt_scripts/change_rule_status.jmx


+ 707 - 0
fvt_scripts/change_stream_rule.jmx

@@ -0,0 +1,707 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<jmeterTestPlan version="1.2" properties="4.0" jmeter="4.0 r1823414">
+  <hashTree>
+    <TestPlan guiclass="TestPlanGui" testclass="TestPlan" testname="Test Plan" enabled="true">
+      <stringProp name="TestPlan.comments">Change the stream definition for rule</stringProp>
+      <boolProp name="TestPlan.functional_mode">false</boolProp>
+      <boolProp name="TestPlan.tearDown_on_shutdown">true</boolProp>
+      <boolProp name="TestPlan.serialize_threadgroups">false</boolProp>
+      <elementProp name="TestPlan.user_defined_variables" elementType="Arguments" guiclass="ArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true">
+        <collectionProp name="Arguments.arguments"/>
+      </elementProp>
+      <stringProp name="TestPlan.user_define_classpath"></stringProp>
+    </TestPlan>
+    <hashTree>
+      <Arguments guiclass="ArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true">
+        <collectionProp name="Arguments.arguments">
+          <elementProp name="srv" elementType="Argument">
+            <stringProp name="Argument.name">srv</stringProp>
+            <stringProp name="Argument.value">127.0.0.1</stringProp>
+            <stringProp name="Argument.metadata">=</stringProp>
+          </elementProp>
+          <elementProp name="rest_port" elementType="Argument">
+            <stringProp name="Argument.name">rest_port</stringProp>
+            <stringProp name="Argument.value">9081</stringProp>
+            <stringProp name="Argument.metadata">=</stringProp>
+          </elementProp>
+          <elementProp name="mqtt_srv" elementType="Argument">
+            <stringProp name="Argument.name">mqtt_srv</stringProp>
+            <stringProp name="Argument.value">127.0.0.1</stringProp>
+            <stringProp name="Argument.metadata">=</stringProp>
+          </elementProp>
+        </collectionProp>
+      </Arguments>
+      <hashTree/>
+      <ThreadGroup guiclass="ThreadGroupGui" testclass="ThreadGroup" testname="Rules" enabled="true">
+        <stringProp name="ThreadGroup.on_sample_error">continue</stringProp>
+        <elementProp name="ThreadGroup.main_controller" elementType="LoopController" guiclass="LoopControlPanel" testclass="LoopController" testname="Loop Controller" enabled="true">
+          <boolProp name="LoopController.continue_forever">false</boolProp>
+          <stringProp name="LoopController.loops">1</stringProp>
+        </elementProp>
+        <stringProp name="ThreadGroup.num_threads">1</stringProp>
+        <stringProp name="ThreadGroup.ramp_time">1</stringProp>
+        <boolProp name="ThreadGroup.scheduler">false</boolProp>
+        <stringProp name="ThreadGroup.duration"></stringProp>
+        <stringProp name="ThreadGroup.delay"></stringProp>
+      </ThreadGroup>
+      <hashTree>
+        <ResultCollector guiclass="ViewResultsFullVisualizer" testclass="ResultCollector" testname="View Results Tree" enabled="true">
+          <boolProp name="ResultCollector.error_logging">false</boolProp>
+          <objProp>
+            <name>saveConfig</name>
+            <value class="SampleSaveConfiguration">
+              <time>true</time>
+              <latency>true</latency>
+              <timestamp>true</timestamp>
+              <success>true</success>
+              <label>true</label>
+              <code>true</code>
+              <message>true</message>
+              <threadName>true</threadName>
+              <dataType>true</dataType>
+              <encoding>false</encoding>
+              <assertions>true</assertions>
+              <subresults>true</subresults>
+              <responseData>false</responseData>
+              <samplerData>false</samplerData>
+              <xml>false</xml>
+              <fieldNames>true</fieldNames>
+              <responseHeaders>false</responseHeaders>
+              <requestHeaders>false</requestHeaders>
+              <responseDataOnError>false</responseDataOnError>
+              <saveAssertionResultsFailureMessage>true</saveAssertionResultsFailureMessage>
+              <assertionsResultsToSave>0</assertionsResultsToSave>
+              <bytes>true</bytes>
+              <sentBytes>true</sentBytes>
+              <threadCounts>true</threadCounts>
+              <idleTime>true</idleTime>
+              <connectTime>true</connectTime>
+            </value>
+          </objProp>
+          <stringProp name="filename"></stringProp>
+        </ResultCollector>
+        <hashTree/>
+        <TransactionController guiclass="TransactionControllerGui" testclass="TransactionController" testname="1stRun" enabled="true">
+          <boolProp name="TransactionController.includeTimers">false</boolProp>
+          <boolProp name="TransactionController.parent">false</boolProp>
+        </TransactionController>
+        <hashTree>
+          <net.xmeter.samplers.ConnectSampler guiclass="net.xmeter.gui.ConnectSamplerUI" testclass="net.xmeter.samplers.ConnectSampler" testname="MQTT Connect" enabled="true">
+            <stringProp name="mqtt.server">${mqtt_srv}</stringProp>
+            <stringProp name="mqtt.port">1883</stringProp>
+            <stringProp name="mqtt.version">3.1</stringProp>
+            <stringProp name="mqtt.conn_timeout">10</stringProp>
+            <boolProp name="mqtt.private_protocol">false</boolProp>
+            <stringProp name="mqtt.listener_timeout">10</stringProp>
+            <stringProp name="mqtt.protocol">TCP</stringProp>
+            <boolProp name="mqtt.dual_ssl_authentication">false</boolProp>
+            <stringProp name="mqtt.keystore_file_path"></stringProp>
+            <stringProp name="mqtt.keystore_password"></stringProp>
+            <stringProp name="mqtt.clientcert_file_path"></stringProp>
+            <stringProp name="mqtt.clientcert_password"></stringProp>
+            <stringProp name="mqtt.user_name"></stringProp>
+            <stringProp name="mqtt.password"></stringProp>
+            <stringProp name="mqtt.client_id_prefix">conn_</stringProp>
+            <boolProp name="mqtt.client_id_suffix">true</boolProp>
+            <stringProp name="mqtt.conn_keep_alive">300</stringProp>
+            <stringProp name="mqtt.conn_attampt_max">0</stringProp>
+            <stringProp name="mqtt.reconn_attampt_max">0</stringProp>
+            <stringProp name="TestPlan.comments">The connections</stringProp>
+          </net.xmeter.samplers.ConnectSampler>
+          <hashTree/>
+          <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="CreateStream" enabled="true">
+            <boolProp name="HTTPSampler.postBodyRaw">true</boolProp>
+            <elementProp name="HTTPsampler.Arguments" elementType="Arguments">
+              <collectionProp name="Arguments.arguments">
+                <elementProp name="" elementType="HTTPArgument">
+                  <boolProp name="HTTPArgument.always_encode">false</boolProp>
+                  <stringProp name="Argument.value">{&#xd;
+&quot;sql&quot; : &quot;create stream demo (temperature float, humidity bigint) WITH (FORMAT=\&quot;JSON\&quot;, DATASOURCE=\&quot;devices/+/messages\&quot; )&quot;&#xd;
+}</stringProp>
+                  <stringProp name="Argument.metadata">=</stringProp>
+                </elementProp>
+              </collectionProp>
+            </elementProp>
+            <stringProp name="HTTPSampler.domain">${srv}</stringProp>
+            <stringProp name="HTTPSampler.port">${rest_port}</stringProp>
+            <stringProp name="HTTPSampler.protocol"></stringProp>
+            <stringProp name="HTTPSampler.contentEncoding"></stringProp>
+            <stringProp name="HTTPSampler.path">/streams</stringProp>
+            <stringProp name="HTTPSampler.method">POST</stringProp>
+            <boolProp name="HTTPSampler.follow_redirects">true</boolProp>
+            <boolProp name="HTTPSampler.auto_redirects">false</boolProp>
+            <boolProp name="HTTPSampler.use_keepalive">true</boolProp>
+            <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp>
+            <stringProp name="HTTPSampler.embedded_url_re"></stringProp>
+            <stringProp name="HTTPSampler.connect_timeout"></stringProp>
+            <stringProp name="HTTPSampler.response_timeout"></stringProp>
+          </HTTPSamplerProxy>
+          <hashTree>
+            <ResponseAssertion guiclass="AssertionGui" testclass="ResponseAssertion" testname="Response Assertion" enabled="true">
+              <collectionProp name="Asserion.test_strings">
+                <stringProp name="-1754954177">Stream demo is created.</stringProp>
+              </collectionProp>
+              <stringProp name="Assertion.custom_message"></stringProp>
+              <stringProp name="Assertion.test_field">Assertion.response_data</stringProp>
+              <boolProp name="Assertion.assume_success">true</boolProp>
+              <intProp name="Assertion.test_type">16</intProp>
+            </ResponseAssertion>
+            <hashTree/>
+          </hashTree>
+          <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="CreateRule" enabled="true">
+            <boolProp name="HTTPSampler.postBodyRaw">true</boolProp>
+            <elementProp name="HTTPsampler.Arguments" elementType="Arguments">
+              <collectionProp name="Arguments.arguments">
+                <elementProp name="" elementType="HTTPArgument">
+                  <boolProp name="HTTPArgument.always_encode">false</boolProp>
+                  <stringProp name="Argument.value">{&#xd;
+  &quot;id&quot;: &quot;rule1&quot;,&#xd;
+  &quot;sql&quot;: &quot;SELECT light FROM demo&quot;,&#xd;
+  &quot;actions&quot;: [&#xd;
+    {&#xd;
+      &quot;mqtt&quot;: {&#xd;
+        &quot;server&quot;: &quot;tcp://${mqtt_srv}:1883&quot;,&#xd;
+        &quot;topic&quot;: &quot;devices/result&quot;,&#xd;
+        &quot;qos&quot;: 1,&#xd;
+        &quot;clientId&quot;: &quot;demo_001&quot;&#xd;
+      }&#xd;
+    }&#xd;
+  ]&#xd;
+}</stringProp>
+                  <stringProp name="Argument.metadata">=</stringProp>
+                </elementProp>
+              </collectionProp>
+            </elementProp>
+            <stringProp name="HTTPSampler.domain">${srv}</stringProp>
+            <stringProp name="HTTPSampler.port">${rest_port}</stringProp>
+            <stringProp name="HTTPSampler.protocol"></stringProp>
+            <stringProp name="HTTPSampler.contentEncoding"></stringProp>
+            <stringProp name="HTTPSampler.path">/rules</stringProp>
+            <stringProp name="HTTPSampler.method">POST</stringProp>
+            <boolProp name="HTTPSampler.follow_redirects">true</boolProp>
+            <boolProp name="HTTPSampler.auto_redirects">false</boolProp>
+            <boolProp name="HTTPSampler.use_keepalive">true</boolProp>
+            <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp>
+            <stringProp name="HTTPSampler.embedded_url_re"></stringProp>
+            <stringProp name="HTTPSampler.connect_timeout"></stringProp>
+            <stringProp name="HTTPSampler.response_timeout"></stringProp>
+          </HTTPSamplerProxy>
+          <hashTree>
+            <ResponseAssertion guiclass="AssertionGui" testclass="ResponseAssertion" testname="Response Assertion" enabled="true">
+              <collectionProp name="Asserion.test_strings">
+                <stringProp name="-2022196798">Rule rule1 was created</stringProp>
+              </collectionProp>
+              <stringProp name="Assertion.custom_message"></stringProp>
+              <stringProp name="Assertion.test_field">Assertion.response_data</stringProp>
+              <boolProp name="Assertion.assume_success">true</boolProp>
+              <intProp name="Assertion.test_type">16</intProp>
+            </ResponseAssertion>
+            <hashTree/>
+          </hashTree>
+          <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="GetRuleStatus" enabled="true">
+            <boolProp name="HTTPSampler.postBodyRaw">true</boolProp>
+            <elementProp name="HTTPsampler.Arguments" elementType="Arguments">
+              <collectionProp name="Arguments.arguments">
+                <elementProp name="" elementType="HTTPArgument">
+                  <boolProp name="HTTPArgument.always_encode">false</boolProp>
+                  <stringProp name="Argument.value"></stringProp>
+                  <stringProp name="Argument.metadata">=</stringProp>
+                </elementProp>
+              </collectionProp>
+            </elementProp>
+            <stringProp name="HTTPSampler.domain">${srv}</stringProp>
+            <stringProp name="HTTPSampler.port">${rest_port}</stringProp>
+            <stringProp name="HTTPSampler.protocol"></stringProp>
+            <stringProp name="HTTPSampler.contentEncoding"></stringProp>
+            <stringProp name="HTTPSampler.path">/rules/rule1/status</stringProp>
+            <stringProp name="HTTPSampler.method">GET</stringProp>
+            <boolProp name="HTTPSampler.follow_redirects">true</boolProp>
+            <boolProp name="HTTPSampler.auto_redirects">false</boolProp>
+            <boolProp name="HTTPSampler.use_keepalive">true</boolProp>
+            <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp>
+            <stringProp name="HTTPSampler.embedded_url_re"></stringProp>
+            <stringProp name="HTTPSampler.connect_timeout"></stringProp>
+            <stringProp name="HTTPSampler.response_timeout"></stringProp>
+          </HTTPSamplerProxy>
+          <hashTree>
+            <JSONPathAssertion guiclass="JSONPathAssertionGui" testclass="JSONPathAssertion" testname="JSON Assertion" enabled="true">
+              <stringProp name="JSON_PATH">$.source_demo_0_records_in_total</stringProp>
+              <stringProp name="EXPECTED_VALUE">0</stringProp>
+              <boolProp name="JSONVALIDATION">true</boolProp>
+              <boolProp name="EXPECT_NULL">false</boolProp>
+              <boolProp name="INVERT">false</boolProp>
+              <boolProp name="ISREGEX">false</boolProp>
+            </JSONPathAssertion>
+            <hashTree/>
+          </hashTree>
+          <net.xmeter.samplers.PubSampler guiclass="net.xmeter.gui.PubSamplerUI" testclass="net.xmeter.samplers.PubSampler" testname="MQTT_Pub_1" enabled="true">
+            <stringProp name="mqtt.topic_name">devices/${device_id}/messages</stringProp>
+            <stringProp name="mqtt.qos_level">0</stringProp>
+            <boolProp name="mqtt.add_timestamp">false</boolProp>
+            <stringProp name="mqtt.message_type">String</stringProp>
+            <stringProp name="mqtt.message_type_fixed_length">1024</stringProp>
+            <stringProp name="mqtt.message_to_sent">{&quot;temperature&quot;: ${__Random(1,100,)}, &quot;humidity&quot; : ${__Random(1,100,)}}</stringProp>
+          </net.xmeter.samplers.PubSampler>
+          <hashTree/>
+          <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="GetRuleStatus" enabled="true">
+            <boolProp name="HTTPSampler.postBodyRaw">true</boolProp>
+            <elementProp name="HTTPsampler.Arguments" elementType="Arguments">
+              <collectionProp name="Arguments.arguments">
+                <elementProp name="" elementType="HTTPArgument">
+                  <boolProp name="HTTPArgument.always_encode">false</boolProp>
+                  <stringProp name="Argument.value"></stringProp>
+                  <stringProp name="Argument.metadata">=</stringProp>
+                </elementProp>
+              </collectionProp>
+            </elementProp>
+            <stringProp name="HTTPSampler.domain">${srv}</stringProp>
+            <stringProp name="HTTPSampler.port">${rest_port}</stringProp>
+            <stringProp name="HTTPSampler.protocol"></stringProp>
+            <stringProp name="HTTPSampler.contentEncoding"></stringProp>
+            <stringProp name="HTTPSampler.path">/rules/rule1/status</stringProp>
+            <stringProp name="HTTPSampler.method">GET</stringProp>
+            <boolProp name="HTTPSampler.follow_redirects">true</boolProp>
+            <boolProp name="HTTPSampler.auto_redirects">false</boolProp>
+            <boolProp name="HTTPSampler.use_keepalive">true</boolProp>
+            <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp>
+            <stringProp name="HTTPSampler.embedded_url_re"></stringProp>
+            <stringProp name="HTTPSampler.connect_timeout"></stringProp>
+            <stringProp name="HTTPSampler.response_timeout"></stringProp>
+          </HTTPSamplerProxy>
+          <hashTree>
+            <JSONPathAssertion guiclass="JSONPathAssertionGui" testclass="JSONPathAssertion" testname="JSON Assertion" enabled="true">
+              <stringProp name="JSON_PATH">$.source_demo_0_records_in_total</stringProp>
+              <stringProp name="EXPECTED_VALUE">1</stringProp>
+              <boolProp name="JSONVALIDATION">true</boolProp>
+              <boolProp name="EXPECT_NULL">false</boolProp>
+              <boolProp name="INVERT">false</boolProp>
+              <boolProp name="ISREGEX">false</boolProp>
+            </JSONPathAssertion>
+            <hashTree/>
+            <JSONPathAssertion guiclass="JSONPathAssertionGui" testclass="JSONPathAssertion" testname="JSON Assertion" enabled="true">
+              <stringProp name="JSON_PATH">$.sink_sink_mqtt_0_records_in_total</stringProp>
+              <stringProp name="EXPECTED_VALUE">1</stringProp>
+              <boolProp name="JSONVALIDATION">true</boolProp>
+              <boolProp name="EXPECT_NULL">false</boolProp>
+              <boolProp name="INVERT">false</boolProp>
+              <boolProp name="ISREGEX">false</boolProp>
+            </JSONPathAssertion>
+            <hashTree/>
+          </hashTree>
+          <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="DropStream" enabled="true">
+            <boolProp name="HTTPSampler.postBodyRaw">true</boolProp>
+            <elementProp name="HTTPsampler.Arguments" elementType="Arguments">
+              <collectionProp name="Arguments.arguments">
+                <elementProp name="" elementType="HTTPArgument">
+                  <boolProp name="HTTPArgument.always_encode">false</boolProp>
+                  <stringProp name="Argument.value"></stringProp>
+                  <stringProp name="Argument.metadata">=</stringProp>
+                </elementProp>
+              </collectionProp>
+            </elementProp>
+            <stringProp name="HTTPSampler.domain">${srv}</stringProp>
+            <stringProp name="HTTPSampler.port">${rest_port}</stringProp>
+            <stringProp name="HTTPSampler.protocol"></stringProp>
+            <stringProp name="HTTPSampler.contentEncoding"></stringProp>
+            <stringProp name="HTTPSampler.path">/streams/demo</stringProp>
+            <stringProp name="HTTPSampler.method">DELETE</stringProp>
+            <boolProp name="HTTPSampler.follow_redirects">true</boolProp>
+            <boolProp name="HTTPSampler.auto_redirects">false</boolProp>
+            <boolProp name="HTTPSampler.use_keepalive">true</boolProp>
+            <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp>
+            <stringProp name="HTTPSampler.embedded_url_re"></stringProp>
+            <stringProp name="HTTPSampler.connect_timeout"></stringProp>
+            <stringProp name="HTTPSampler.response_timeout"></stringProp>
+          </HTTPSamplerProxy>
+          <hashTree>
+            <ResponseAssertion guiclass="AssertionGui" testclass="ResponseAssertion" testname="Response Assertion" enabled="true">
+              <collectionProp name="Asserion.test_strings">
+                <stringProp name="287881319">Stream demo is dropped.</stringProp>
+              </collectionProp>
+              <stringProp name="Assertion.custom_message"></stringProp>
+              <stringProp name="Assertion.test_field">Assertion.response_data</stringProp>
+              <boolProp name="Assertion.assume_success">false</boolProp>
+              <intProp name="Assertion.test_type">16</intProp>
+            </ResponseAssertion>
+            <hashTree/>
+          </hashTree>
+          <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="CreateStream" enabled="true">
+            <boolProp name="HTTPSampler.postBodyRaw">true</boolProp>
+            <elementProp name="HTTPsampler.Arguments" elementType="Arguments">
+              <collectionProp name="Arguments.arguments">
+                <elementProp name="" elementType="HTTPArgument">
+                  <boolProp name="HTTPArgument.always_encode">false</boolProp>
+                  <stringProp name="Argument.value">{&#xd;
+&quot;sql&quot; : &quot;create stream demo (temperature float, humidity bigint, light bigint) WITH (FORMAT=\&quot;JSON\&quot;, DATASOURCE=\&quot;devices/+/messages\&quot; )&quot;&#xd;
+}</stringProp>
+                  <stringProp name="Argument.metadata">=</stringProp>
+                </elementProp>
+              </collectionProp>
+            </elementProp>
+            <stringProp name="HTTPSampler.domain">${srv}</stringProp>
+            <stringProp name="HTTPSampler.port">${rest_port}</stringProp>
+            <stringProp name="HTTPSampler.protocol"></stringProp>
+            <stringProp name="HTTPSampler.contentEncoding"></stringProp>
+            <stringProp name="HTTPSampler.path">/streams</stringProp>
+            <stringProp name="HTTPSampler.method">POST</stringProp>
+            <boolProp name="HTTPSampler.follow_redirects">true</boolProp>
+            <boolProp name="HTTPSampler.auto_redirects">false</boolProp>
+            <boolProp name="HTTPSampler.use_keepalive">true</boolProp>
+            <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp>
+            <stringProp name="HTTPSampler.embedded_url_re"></stringProp>
+            <stringProp name="HTTPSampler.connect_timeout"></stringProp>
+            <stringProp name="HTTPSampler.response_timeout"></stringProp>
+          </HTTPSamplerProxy>
+          <hashTree>
+            <ResponseAssertion guiclass="AssertionGui" testclass="ResponseAssertion" testname="Response Assertion" enabled="true">
+              <collectionProp name="Asserion.test_strings">
+                <stringProp name="-1754954177">Stream demo is created.</stringProp>
+              </collectionProp>
+              <stringProp name="Assertion.custom_message"></stringProp>
+              <stringProp name="Assertion.test_field">Assertion.response_data</stringProp>
+              <boolProp name="Assertion.assume_success">true</boolProp>
+              <intProp name="Assertion.test_type">16</intProp>
+            </ResponseAssertion>
+            <hashTree/>
+          </hashTree>
+          <ConstantTimer guiclass="ConstantTimerGui" testclass="ConstantTimer" testname="Constant Timer" enabled="true">
+            <stringProp name="ConstantTimer.delay">500</stringProp>
+          </ConstantTimer>
+          <hashTree/>
+        </hashTree>
+        <TransactionController guiclass="TransactionControllerGui" testclass="TransactionController" testname="RedefineStreamAndRun" enabled="true">
+          <boolProp name="TransactionController.includeTimers">false</boolProp>
+          <boolProp name="TransactionController.parent">false</boolProp>
+        </TransactionController>
+        <hashTree>
+          <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="DropStream" enabled="true">
+            <boolProp name="HTTPSampler.postBodyRaw">true</boolProp>
+            <elementProp name="HTTPsampler.Arguments" elementType="Arguments">
+              <collectionProp name="Arguments.arguments">
+                <elementProp name="" elementType="HTTPArgument">
+                  <boolProp name="HTTPArgument.always_encode">false</boolProp>
+                  <stringProp name="Argument.value"></stringProp>
+                  <stringProp name="Argument.metadata">=</stringProp>
+                </elementProp>
+              </collectionProp>
+            </elementProp>
+            <stringProp name="HTTPSampler.domain">${srv}</stringProp>
+            <stringProp name="HTTPSampler.port">${rest_port}</stringProp>
+            <stringProp name="HTTPSampler.protocol"></stringProp>
+            <stringProp name="HTTPSampler.contentEncoding"></stringProp>
+            <stringProp name="HTTPSampler.path">/streams/demo</stringProp>
+            <stringProp name="HTTPSampler.method">DELETE</stringProp>
+            <boolProp name="HTTPSampler.follow_redirects">true</boolProp>
+            <boolProp name="HTTPSampler.auto_redirects">false</boolProp>
+            <boolProp name="HTTPSampler.use_keepalive">true</boolProp>
+            <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp>
+            <stringProp name="HTTPSampler.embedded_url_re"></stringProp>
+            <stringProp name="HTTPSampler.connect_timeout"></stringProp>
+            <stringProp name="HTTPSampler.response_timeout"></stringProp>
+          </HTTPSamplerProxy>
+          <hashTree>
+            <ResponseAssertion guiclass="AssertionGui" testclass="ResponseAssertion" testname="Response Assertion" enabled="true">
+              <collectionProp name="Asserion.test_strings">
+                <stringProp name="287881319">Stream demo is dropped.</stringProp>
+              </collectionProp>
+              <stringProp name="Assertion.custom_message"></stringProp>
+              <stringProp name="Assertion.test_field">Assertion.response_data</stringProp>
+              <boolProp name="Assertion.assume_success">false</boolProp>
+              <intProp name="Assertion.test_type">16</intProp>
+            </ResponseAssertion>
+            <hashTree/>
+          </hashTree>
+          <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="CreateStream" enabled="true">
+            <boolProp name="HTTPSampler.postBodyRaw">true</boolProp>
+            <elementProp name="HTTPsampler.Arguments" elementType="Arguments">
+              <collectionProp name="Arguments.arguments">
+                <elementProp name="" elementType="HTTPArgument">
+                  <boolProp name="HTTPArgument.always_encode">false</boolProp>
+                  <stringProp name="Argument.value">{&#xd;
+&quot;sql&quot; : &quot;create stream demo (temperature float, humidity bigint, light bigint) WITH (FORMAT=\&quot;JSON\&quot;, DATASOURCE=\&quot;devices/+/messages\&quot; )&quot;&#xd;
+}</stringProp>
+                  <stringProp name="Argument.metadata">=</stringProp>
+                </elementProp>
+              </collectionProp>
+            </elementProp>
+            <stringProp name="HTTPSampler.domain">${srv}</stringProp>
+            <stringProp name="HTTPSampler.port">${rest_port}</stringProp>
+            <stringProp name="HTTPSampler.protocol"></stringProp>
+            <stringProp name="HTTPSampler.contentEncoding"></stringProp>
+            <stringProp name="HTTPSampler.path">/streams</stringProp>
+            <stringProp name="HTTPSampler.method">POST</stringProp>
+            <boolProp name="HTTPSampler.follow_redirects">true</boolProp>
+            <boolProp name="HTTPSampler.auto_redirects">false</boolProp>
+            <boolProp name="HTTPSampler.use_keepalive">true</boolProp>
+            <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp>
+            <stringProp name="HTTPSampler.embedded_url_re"></stringProp>
+            <stringProp name="HTTPSampler.connect_timeout"></stringProp>
+            <stringProp name="HTTPSampler.response_timeout"></stringProp>
+          </HTTPSamplerProxy>
+          <hashTree>
+            <ResponseAssertion guiclass="AssertionGui" testclass="ResponseAssertion" testname="Response Assertion" enabled="true">
+              <collectionProp name="Asserion.test_strings">
+                <stringProp name="-1754954177">Stream demo is created.</stringProp>
+              </collectionProp>
+              <stringProp name="Assertion.custom_message"></stringProp>
+              <stringProp name="Assertion.test_field">Assertion.response_data</stringProp>
+              <boolProp name="Assertion.assume_success">true</boolProp>
+              <intProp name="Assertion.test_type">16</intProp>
+            </ResponseAssertion>
+            <hashTree/>
+          </hashTree>
+          <net.xmeter.samplers.PubSampler guiclass="net.xmeter.gui.PubSamplerUI" testclass="net.xmeter.samplers.PubSampler" testname="MQTT_Pub_2" enabled="true">
+            <stringProp name="mqtt.topic_name">devices/${device_id}/messages</stringProp>
+            <stringProp name="mqtt.qos_level">0</stringProp>
+            <boolProp name="mqtt.add_timestamp">false</boolProp>
+            <stringProp name="mqtt.message_type">String</stringProp>
+            <stringProp name="mqtt.message_type_fixed_length">1024</stringProp>
+            <stringProp name="mqtt.message_to_sent">{&quot;temperature&quot;: ${__Random(1,100,)}, &quot;humidity&quot; : ${__Random(1,100,)}, &quot;light&quot; : ${__Random(1,100,)}}</stringProp>
+          </net.xmeter.samplers.PubSampler>
+          <hashTree/>
+          <ConstantTimer guiclass="ConstantTimerGui" testclass="ConstantTimer" testname="Constant Timer" enabled="true">
+            <stringProp name="ConstantTimer.delay">500</stringProp>
+          </ConstantTimer>
+          <hashTree/>
+        </hashTree>
+        <TransactionController guiclass="TransactionControllerGui" testclass="TransactionController" testname="RestartRuleAndRun" enabled="true">
+          <boolProp name="TransactionController.includeTimers">false</boolProp>
+          <boolProp name="TransactionController.parent">false</boolProp>
+        </TransactionController>
+        <hashTree>
+          <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="RestartRule" enabled="true">
+            <boolProp name="HTTPSampler.postBodyRaw">true</boolProp>
+            <elementProp name="HTTPsampler.Arguments" elementType="Arguments">
+              <collectionProp name="Arguments.arguments">
+                <elementProp name="" elementType="HTTPArgument">
+                  <boolProp name="HTTPArgument.always_encode">false</boolProp>
+                  <stringProp name="Argument.value"></stringProp>
+                  <stringProp name="Argument.metadata">=</stringProp>
+                </elementProp>
+              </collectionProp>
+            </elementProp>
+            <stringProp name="HTTPSampler.domain">${srv}</stringProp>
+            <stringProp name="HTTPSampler.port">${rest_port}</stringProp>
+            <stringProp name="HTTPSampler.protocol"></stringProp>
+            <stringProp name="HTTPSampler.contentEncoding"></stringProp>
+            <stringProp name="HTTPSampler.path">/rules/rule1/restart</stringProp>
+            <stringProp name="HTTPSampler.method">POST</stringProp>
+            <boolProp name="HTTPSampler.follow_redirects">true</boolProp>
+            <boolProp name="HTTPSampler.auto_redirects">false</boolProp>
+            <boolProp name="HTTPSampler.use_keepalive">true</boolProp>
+            <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp>
+            <stringProp name="HTTPSampler.embedded_url_re"></stringProp>
+            <stringProp name="HTTPSampler.connect_timeout"></stringProp>
+            <stringProp name="HTTPSampler.response_timeout"></stringProp>
+          </HTTPSamplerProxy>
+          <hashTree>
+            <ResponseAssertion guiclass="AssertionGui" testclass="ResponseAssertion" testname="Response Assertion" enabled="true">
+              <collectionProp name="Asserion.test_strings">
+                <stringProp name="-167686328">Rule rule1 was restarted</stringProp>
+              </collectionProp>
+              <stringProp name="Assertion.custom_message"></stringProp>
+              <stringProp name="Assertion.test_field">Assertion.response_data</stringProp>
+              <boolProp name="Assertion.assume_success">false</boolProp>
+              <intProp name="Assertion.test_type">16</intProp>
+            </ResponseAssertion>
+            <hashTree/>
+          </hashTree>
+          <net.xmeter.samplers.PubSampler guiclass="net.xmeter.gui.PubSamplerUI" testclass="net.xmeter.samplers.PubSampler" testname="MQTT_Pub_3" enabled="true">
+            <stringProp name="mqtt.topic_name">devices/${device_id}/messages</stringProp>
+            <stringProp name="mqtt.qos_level">0</stringProp>
+            <boolProp name="mqtt.add_timestamp">false</boolProp>
+            <stringProp name="mqtt.message_type">String</stringProp>
+            <stringProp name="mqtt.message_type_fixed_length">1024</stringProp>
+            <stringProp name="mqtt.message_to_sent">{&quot;temperature&quot;: ${__Random(1,100,)}, &quot;humidity&quot; : ${__Random(1,100,)}, &quot;light&quot; : ${__Random(1,100,)}}</stringProp>
+          </net.xmeter.samplers.PubSampler>
+          <hashTree/>
+          <ConstantTimer guiclass="ConstantTimerGui" testclass="ConstantTimer" testname="Constant Timer" enabled="true">
+            <stringProp name="ConstantTimer.delay">500</stringProp>
+          </ConstantTimer>
+          <hashTree/>
+        </hashTree>
+        <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="DropStream" enabled="true">
+          <boolProp name="HTTPSampler.postBodyRaw">true</boolProp>
+          <elementProp name="HTTPsampler.Arguments" elementType="Arguments">
+            <collectionProp name="Arguments.arguments">
+              <elementProp name="" elementType="HTTPArgument">
+                <boolProp name="HTTPArgument.always_encode">false</boolProp>
+                <stringProp name="Argument.value"></stringProp>
+                <stringProp name="Argument.metadata">=</stringProp>
+              </elementProp>
+            </collectionProp>
+          </elementProp>
+          <stringProp name="HTTPSampler.domain">${srv}</stringProp>
+          <stringProp name="HTTPSampler.port">${rest_port}</stringProp>
+          <stringProp name="HTTPSampler.protocol"></stringProp>
+          <stringProp name="HTTPSampler.contentEncoding"></stringProp>
+          <stringProp name="HTTPSampler.path">/streams/demo</stringProp>
+          <stringProp name="HTTPSampler.method">DELETE</stringProp>
+          <boolProp name="HTTPSampler.follow_redirects">true</boolProp>
+          <boolProp name="HTTPSampler.auto_redirects">false</boolProp>
+          <boolProp name="HTTPSampler.use_keepalive">true</boolProp>
+          <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp>
+          <stringProp name="HTTPSampler.embedded_url_re"></stringProp>
+          <stringProp name="HTTPSampler.connect_timeout"></stringProp>
+          <stringProp name="HTTPSampler.response_timeout"></stringProp>
+        </HTTPSamplerProxy>
+        <hashTree>
+          <ResponseAssertion guiclass="AssertionGui" testclass="ResponseAssertion" testname="Response Assertion" enabled="true">
+            <collectionProp name="Asserion.test_strings">
+              <stringProp name="287881319">Stream demo is dropped.</stringProp>
+            </collectionProp>
+            <stringProp name="Assertion.custom_message"></stringProp>
+            <stringProp name="Assertion.test_field">Assertion.response_data</stringProp>
+            <boolProp name="Assertion.assume_success">false</boolProp>
+            <intProp name="Assertion.test_type">16</intProp>
+          </ResponseAssertion>
+          <hashTree/>
+        </hashTree>
+        <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="DropRule" enabled="true">
+          <boolProp name="HTTPSampler.postBodyRaw">true</boolProp>
+          <elementProp name="HTTPsampler.Arguments" elementType="Arguments">
+            <collectionProp name="Arguments.arguments">
+              <elementProp name="" elementType="HTTPArgument">
+                <boolProp name="HTTPArgument.always_encode">false</boolProp>
+                <stringProp name="Argument.value"></stringProp>
+                <stringProp name="Argument.metadata">=</stringProp>
+              </elementProp>
+            </collectionProp>
+          </elementProp>
+          <stringProp name="HTTPSampler.domain">${srv}</stringProp>
+          <stringProp name="HTTPSampler.port">${rest_port}</stringProp>
+          <stringProp name="HTTPSampler.protocol"></stringProp>
+          <stringProp name="HTTPSampler.contentEncoding"></stringProp>
+          <stringProp name="HTTPSampler.path">/rules/rule1</stringProp>
+          <stringProp name="HTTPSampler.method">DELETE</stringProp>
+          <boolProp name="HTTPSampler.follow_redirects">true</boolProp>
+          <boolProp name="HTTPSampler.auto_redirects">false</boolProp>
+          <boolProp name="HTTPSampler.use_keepalive">true</boolProp>
+          <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp>
+          <stringProp name="HTTPSampler.embedded_url_re"></stringProp>
+          <stringProp name="HTTPSampler.connect_timeout"></stringProp>
+          <stringProp name="HTTPSampler.response_timeout"></stringProp>
+        </HTTPSamplerProxy>
+        <hashTree>
+          <ResponseAssertion guiclass="AssertionGui" testclass="ResponseAssertion" testname="Response Assertion" enabled="true">
+            <collectionProp name="Asserion.test_strings">
+              <stringProp name="717250485">Rule rule1 is dropped.</stringProp>
+            </collectionProp>
+            <stringProp name="Assertion.custom_message"></stringProp>
+            <stringProp name="Assertion.test_field">Assertion.response_data</stringProp>
+            <boolProp name="Assertion.assume_success">false</boolProp>
+            <intProp name="Assertion.test_type">16</intProp>
+          </ResponseAssertion>
+          <hashTree/>
+        </hashTree>
+      </hashTree>
+      <ThreadGroup guiclass="ThreadGroupGui" testclass="ThreadGroup" testname="Result" enabled="true">
+        <stringProp name="ThreadGroup.on_sample_error">continue</stringProp>
+        <elementProp name="ThreadGroup.main_controller" elementType="LoopController" guiclass="LoopControlPanel" testclass="LoopController" testname="Loop Controller" enabled="true">
+          <boolProp name="LoopController.continue_forever">false</boolProp>
+          <stringProp name="LoopController.loops">3</stringProp>
+        </elementProp>
+        <stringProp name="ThreadGroup.num_threads">1</stringProp>
+        <stringProp name="ThreadGroup.ramp_time">1</stringProp>
+        <boolProp name="ThreadGroup.scheduler">false</boolProp>
+        <stringProp name="ThreadGroup.duration"></stringProp>
+        <stringProp name="ThreadGroup.delay"></stringProp>
+      </ThreadGroup>
+      <hashTree>
+        <CSVDataSet guiclass="TestBeanGUI" testclass="CSVDataSet" testname="CSV Data Set Config" enabled="true">
+          <stringProp name="filename">change_stream_rule.txt</stringProp>
+          <stringProp name="fileEncoding"></stringProp>
+          <stringProp name="variableNames">result</stringProp>
+          <boolProp name="ignoreFirstLine">false</boolProp>
+          <stringProp name="delimiter">,</stringProp>
+          <boolProp name="quotedData">false</boolProp>
+          <boolProp name="recycle">true</boolProp>
+          <boolProp name="stopThread">false</boolProp>
+          <stringProp name="shareMode">shareMode.all</stringProp>
+        </CSVDataSet>
+        <hashTree/>
+        <ResultCollector guiclass="ViewResultsFullVisualizer" testclass="ResultCollector" testname="View Results Tree" enabled="true">
+          <boolProp name="ResultCollector.error_logging">false</boolProp>
+          <objProp>
+            <name>saveConfig</name>
+            <value class="SampleSaveConfiguration">
+              <time>true</time>
+              <latency>true</latency>
+              <timestamp>true</timestamp>
+              <success>true</success>
+              <label>true</label>
+              <code>true</code>
+              <message>true</message>
+              <threadName>true</threadName>
+              <dataType>true</dataType>
+              <encoding>false</encoding>
+              <assertions>true</assertions>
+              <subresults>true</subresults>
+              <responseData>false</responseData>
+              <samplerData>false</samplerData>
+              <xml>false</xml>
+              <fieldNames>true</fieldNames>
+              <responseHeaders>false</responseHeaders>
+              <requestHeaders>false</requestHeaders>
+              <responseDataOnError>false</responseDataOnError>
+              <saveAssertionResultsFailureMessage>true</saveAssertionResultsFailureMessage>
+              <assertionsResultsToSave>0</assertionsResultsToSave>
+              <bytes>true</bytes>
+              <sentBytes>true</sentBytes>
+              <threadCounts>true</threadCounts>
+              <idleTime>true</idleTime>
+              <connectTime>true</connectTime>
+            </value>
+          </objProp>
+          <stringProp name="filename"></stringProp>
+        </ResultCollector>
+        <hashTree/>
+        <OnceOnlyController guiclass="OnceOnlyControllerGui" testclass="OnceOnlyController" testname="Once Only Controller" enabled="true"/>
+        <hashTree>
+          <net.xmeter.samplers.ConnectSampler guiclass="net.xmeter.gui.ConnectSamplerUI" testclass="net.xmeter.samplers.ConnectSampler" testname="MQTT Connect" enabled="true">
+            <stringProp name="mqtt.server">${mqtt_srv}</stringProp>
+            <stringProp name="mqtt.port">1883</stringProp>
+            <stringProp name="mqtt.version">3.1</stringProp>
+            <stringProp name="mqtt.conn_timeout">10</stringProp>
+            <boolProp name="mqtt.private_protocol">false</boolProp>
+            <stringProp name="mqtt.listener_timeout">10</stringProp>
+            <stringProp name="mqtt.protocol">TCP</stringProp>
+            <boolProp name="mqtt.dual_ssl_authentication">false</boolProp>
+            <stringProp name="mqtt.keystore_file_path"></stringProp>
+            <stringProp name="mqtt.keystore_password"></stringProp>
+            <stringProp name="mqtt.clientcert_file_path"></stringProp>
+            <stringProp name="mqtt.clientcert_password"></stringProp>
+            <stringProp name="mqtt.user_name"></stringProp>
+            <stringProp name="mqtt.password"></stringProp>
+            <stringProp name="mqtt.client_id_prefix">conn_</stringProp>
+            <boolProp name="mqtt.client_id_suffix">true</boolProp>
+            <stringProp name="mqtt.conn_keep_alive">300</stringProp>
+            <stringProp name="mqtt.conn_attampt_max">0</stringProp>
+            <stringProp name="mqtt.reconn_attampt_max">0</stringProp>
+          </net.xmeter.samplers.ConnectSampler>
+          <hashTree/>
+        </hashTree>
+        <net.xmeter.samplers.SubSampler guiclass="net.xmeter.gui.SubSamplerUI" testclass="net.xmeter.samplers.SubSampler" testname="AnalysisResult" enabled="true">
+          <stringProp name="mqtt.topic_name">devices/result</stringProp>
+          <stringProp name="mqtt.qos_level">0</stringProp>
+          <boolProp name="mqtt.add_timestamp">false</boolProp>
+          <boolProp name="mqtt.debug_response">true</boolProp>
+          <stringProp name="mqtt.sample_condition">number of received messages</stringProp>
+          <stringProp name="mqtt.sample_condition_value">1</stringProp>
+        </net.xmeter.samplers.SubSampler>
+        <hashTree>
+          <ResponseAssertion guiclass="AssertionGui" testclass="ResponseAssertion" testname="Response Assertion" enabled="true">
+            <collectionProp name="Asserion.test_strings">
+              <stringProp name="71001929">${result}</stringProp>
+            </collectionProp>
+            <stringProp name="Assertion.custom_message"></stringProp>
+            <stringProp name="Assertion.test_field">Assertion.response_data</stringProp>
+            <boolProp name="Assertion.assume_success">false</boolProp>
+            <intProp name="Assertion.test_type">16</intProp>
+          </ResponseAssertion>
+          <hashTree/>
+        </hashTree>
+      </hashTree>
+    </hashTree>
+  </hashTree>
+</jmeterTestPlan>

+ 3 - 0
fvt_scripts/change_stream_rule.txt

@@ -0,0 +1,3 @@
+[{}]
+[{}]
+light

+ 10 - 0
fvt_scripts/iot_data.txt

@@ -0,0 +1,10 @@
+1,20,30
+2,31,40
+1,35,50
+2,20,30
+1,80,90
+2,45,20
+1,10,90
+2,12,30
+1,65,35
+2,55,32

二进制
fvt_scripts/resources/jmeter_variables.png


+ 16 - 0
fvt_scripts/rule1.txt

@@ -0,0 +1,16 @@
+{
+  "sql": "SELECT * FROM demo",
+  "actions": [
+    {
+      "log": {}
+    },
+    {
+      "mqtt": {
+        "server": "tcp://127.0.0.1:1883",
+        "topic": "devices/result",
+        "qos": 1,
+        "clientId": "demo_001"
+      }
+    }
+  ]
+}

+ 676 - 0
fvt_scripts/rule_test.jmx

@@ -0,0 +1,676 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<jmeterTestPlan version="1.2" properties="4.0" jmeter="4.0 r1823414">
+  <hashTree>
+    <TestPlan guiclass="TestPlanGui" testclass="TestPlan" testname="Test Plan" enabled="true">
+      <stringProp name="TestPlan.comments"></stringProp>
+      <boolProp name="TestPlan.functional_mode">false</boolProp>
+      <boolProp name="TestPlan.tearDown_on_shutdown">true</boolProp>
+      <boolProp name="TestPlan.serialize_threadgroups">false</boolProp>
+      <elementProp name="TestPlan.user_defined_variables" elementType="Arguments" guiclass="ArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true">
+        <collectionProp name="Arguments.arguments"/>
+      </elementProp>
+      <stringProp name="TestPlan.user_define_classpath"></stringProp>
+    </TestPlan>
+    <hashTree>
+      <Arguments guiclass="ArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true">
+        <collectionProp name="Arguments.arguments">
+          <elementProp name="srv" elementType="Argument">
+            <stringProp name="Argument.name">srv</stringProp>
+            <stringProp name="Argument.value">127.0.0.1</stringProp>
+            <stringProp name="Argument.metadata">=</stringProp>
+          </elementProp>
+          <elementProp name="rest_port" elementType="Argument">
+            <stringProp name="Argument.name">rest_port</stringProp>
+            <stringProp name="Argument.value">9081</stringProp>
+            <stringProp name="Argument.metadata">=</stringProp>
+          </elementProp>
+          <elementProp name="k_home" elementType="Argument">
+            <stringProp name="Argument.name">k_home</stringProp>
+            <stringProp name="Argument.value">${__property(base,,)}</stringProp>
+            <stringProp name="Argument.metadata">=</stringProp>
+          </elementProp>
+          <elementProp name="fvt" elementType="Argument">
+            <stringProp name="Argument.name">fvt</stringProp>
+            <stringProp name="Argument.value">${__property(fvt,,)}</stringProp>
+            <stringProp name="Argument.metadata">=</stringProp>
+          </elementProp>
+        </collectionProp>
+      </Arguments>
+      <hashTree/>
+      <ThreadGroup guiclass="ThreadGroupGui" testclass="ThreadGroup" testname="Rules" enabled="true">
+        <stringProp name="ThreadGroup.on_sample_error">continue</stringProp>
+        <elementProp name="ThreadGroup.main_controller" elementType="LoopController" guiclass="LoopControlPanel" testclass="LoopController" testname="Loop Controller" enabled="true">
+          <boolProp name="LoopController.continue_forever">false</boolProp>
+          <stringProp name="LoopController.loops">1</stringProp>
+        </elementProp>
+        <stringProp name="ThreadGroup.num_threads">1</stringProp>
+        <stringProp name="ThreadGroup.ramp_time">1</stringProp>
+        <boolProp name="ThreadGroup.scheduler">false</boolProp>
+        <stringProp name="ThreadGroup.duration"></stringProp>
+        <stringProp name="ThreadGroup.delay"></stringProp>
+      </ThreadGroup>
+      <hashTree>
+        <TransactionController guiclass="TransactionControllerGui" testclass="TransactionController" testname="API" enabled="true">
+          <boolProp name="TransactionController.includeTimers">false</boolProp>
+          <boolProp name="TransactionController.parent">false</boolProp>
+        </TransactionController>
+        <hashTree>
+          <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="API_CreateStream" enabled="true">
+            <boolProp name="HTTPSampler.postBodyRaw">true</boolProp>
+            <elementProp name="HTTPsampler.Arguments" elementType="Arguments">
+              <collectionProp name="Arguments.arguments">
+                <elementProp name="" elementType="HTTPArgument">
+                  <boolProp name="HTTPArgument.always_encode">false</boolProp>
+                  <stringProp name="Argument.value">{&#xd;
+&quot;sql&quot; : &quot;create stream demo (temperature float, humidity bigint) WITH (FORMAT=\&quot;JSON\&quot;, DATASOURCE=\&quot;devices/+/messages\&quot; )&quot;&#xd;
+}</stringProp>
+                  <stringProp name="Argument.metadata">=</stringProp>
+                </elementProp>
+              </collectionProp>
+            </elementProp>
+            <stringProp name="HTTPSampler.domain">${srv}</stringProp>
+            <stringProp name="HTTPSampler.port">${rest_port}</stringProp>
+            <stringProp name="HTTPSampler.protocol"></stringProp>
+            <stringProp name="HTTPSampler.contentEncoding"></stringProp>
+            <stringProp name="HTTPSampler.path">/streams</stringProp>
+            <stringProp name="HTTPSampler.method">POST</stringProp>
+            <boolProp name="HTTPSampler.follow_redirects">true</boolProp>
+            <boolProp name="HTTPSampler.auto_redirects">false</boolProp>
+            <boolProp name="HTTPSampler.use_keepalive">true</boolProp>
+            <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp>
+            <stringProp name="HTTPSampler.embedded_url_re"></stringProp>
+            <stringProp name="HTTPSampler.connect_timeout"></stringProp>
+            <stringProp name="HTTPSampler.response_timeout"></stringProp>
+          </HTTPSamplerProxy>
+          <hashTree>
+            <ResponseAssertion guiclass="AssertionGui" testclass="ResponseAssertion" testname="Response Assertion" enabled="true">
+              <collectionProp name="Asserion.test_strings">
+                <stringProp name="-1754954177">Stream demo is created.</stringProp>
+              </collectionProp>
+              <stringProp name="Assertion.custom_message"></stringProp>
+              <stringProp name="Assertion.test_field">Assertion.response_data</stringProp>
+              <boolProp name="Assertion.assume_success">true</boolProp>
+              <intProp name="Assertion.test_type">16</intProp>
+            </ResponseAssertion>
+            <hashTree/>
+          </hashTree>
+          <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="API_CreateRule" enabled="true">
+            <boolProp name="HTTPSampler.postBodyRaw">true</boolProp>
+            <elementProp name="HTTPsampler.Arguments" elementType="Arguments">
+              <collectionProp name="Arguments.arguments">
+                <elementProp name="" elementType="HTTPArgument">
+                  <boolProp name="HTTPArgument.always_encode">false</boolProp>
+                  <stringProp name="Argument.value">{&#xd;
+  &quot;id&quot;: &quot;rule1&quot;,&#xd;
+  &quot;sql&quot;: &quot;SELECT * FROM demo&quot;,&#xd;
+  &quot;actions&quot;: [&#xd;
+    {&#xd;
+      &quot;log&quot;: {}&#xd;
+    },&#xd;
+    {&#xd;
+      &quot;mqtt&quot;: {&#xd;
+        &quot;server&quot;: &quot;tcp://127.0.0.1:1883&quot;,&#xd;
+        &quot;topic&quot;: &quot;devices/result&quot;,&#xd;
+        &quot;qos&quot;: 1,&#xd;
+        &quot;clientId&quot;: &quot;demo_001&quot;&#xd;
+      }&#xd;
+    }&#xd;
+  ]&#xd;
+}</stringProp>
+                  <stringProp name="Argument.metadata">=</stringProp>
+                </elementProp>
+              </collectionProp>
+            </elementProp>
+            <stringProp name="HTTPSampler.domain">${srv}</stringProp>
+            <stringProp name="HTTPSampler.port">${rest_port}</stringProp>
+            <stringProp name="HTTPSampler.protocol"></stringProp>
+            <stringProp name="HTTPSampler.contentEncoding"></stringProp>
+            <stringProp name="HTTPSampler.path">/rules</stringProp>
+            <stringProp name="HTTPSampler.method">POST</stringProp>
+            <boolProp name="HTTPSampler.follow_redirects">true</boolProp>
+            <boolProp name="HTTPSampler.auto_redirects">false</boolProp>
+            <boolProp name="HTTPSampler.use_keepalive">true</boolProp>
+            <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp>
+            <stringProp name="HTTPSampler.embedded_url_re"></stringProp>
+            <stringProp name="HTTPSampler.connect_timeout"></stringProp>
+            <stringProp name="HTTPSampler.response_timeout"></stringProp>
+          </HTTPSamplerProxy>
+          <hashTree>
+            <ResponseAssertion guiclass="AssertionGui" testclass="ResponseAssertion" testname="Response Assertion" enabled="true">
+              <collectionProp name="Asserion.test_strings">
+                <stringProp name="-2022196798">Rule rule1 was created</stringProp>
+              </collectionProp>
+              <stringProp name="Assertion.custom_message"></stringProp>
+              <stringProp name="Assertion.test_field">Assertion.response_data</stringProp>
+              <boolProp name="Assertion.assume_success">true</boolProp>
+              <intProp name="Assertion.test_type">16</intProp>
+            </ResponseAssertion>
+            <hashTree/>
+          </hashTree>
+          <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="API_ShowRules" enabled="true">
+            <boolProp name="HTTPSampler.postBodyRaw">true</boolProp>
+            <elementProp name="HTTPsampler.Arguments" elementType="Arguments">
+              <collectionProp name="Arguments.arguments">
+                <elementProp name="" elementType="HTTPArgument">
+                  <boolProp name="HTTPArgument.always_encode">false</boolProp>
+                  <stringProp name="Argument.value"></stringProp>
+                  <stringProp name="Argument.metadata">=</stringProp>
+                </elementProp>
+              </collectionProp>
+            </elementProp>
+            <stringProp name="HTTPSampler.domain">${srv}</stringProp>
+            <stringProp name="HTTPSampler.port">${rest_port}</stringProp>
+            <stringProp name="HTTPSampler.protocol"></stringProp>
+            <stringProp name="HTTPSampler.contentEncoding"></stringProp>
+            <stringProp name="HTTPSampler.path">/rules</stringProp>
+            <stringProp name="HTTPSampler.method">GET</stringProp>
+            <boolProp name="HTTPSampler.follow_redirects">true</boolProp>
+            <boolProp name="HTTPSampler.auto_redirects">false</boolProp>
+            <boolProp name="HTTPSampler.use_keepalive">true</boolProp>
+            <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp>
+            <stringProp name="HTTPSampler.embedded_url_re"></stringProp>
+            <stringProp name="HTTPSampler.connect_timeout"></stringProp>
+            <stringProp name="HTTPSampler.response_timeout"></stringProp>
+          </HTTPSamplerProxy>
+          <hashTree>
+            <JSONPathAssertion guiclass="JSONPathAssertionGui" testclass="JSONPathAssertion" testname="JSON Assertion" enabled="true">
+              <stringProp name="JSON_PATH">$[0]</stringProp>
+              <stringProp name="EXPECTED_VALUE">rule1</stringProp>
+              <boolProp name="JSONVALIDATION">false</boolProp>
+              <boolProp name="EXPECT_NULL">false</boolProp>
+              <boolProp name="INVERT">false</boolProp>
+              <boolProp name="ISREGEX">true</boolProp>
+            </JSONPathAssertion>
+            <hashTree/>
+          </hashTree>
+          <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="API_DescribeRule" enabled="true">
+            <boolProp name="HTTPSampler.postBodyRaw">true</boolProp>
+            <elementProp name="HTTPsampler.Arguments" elementType="Arguments">
+              <collectionProp name="Arguments.arguments">
+                <elementProp name="" elementType="HTTPArgument">
+                  <boolProp name="HTTPArgument.always_encode">false</boolProp>
+                  <stringProp name="Argument.value"></stringProp>
+                  <stringProp name="Argument.metadata">=</stringProp>
+                </elementProp>
+              </collectionProp>
+            </elementProp>
+            <stringProp name="HTTPSampler.domain">${srv}</stringProp>
+            <stringProp name="HTTPSampler.port">${rest_port}</stringProp>
+            <stringProp name="HTTPSampler.protocol"></stringProp>
+            <stringProp name="HTTPSampler.contentEncoding"></stringProp>
+            <stringProp name="HTTPSampler.path">/rules/rule1</stringProp>
+            <stringProp name="HTTPSampler.method">GET</stringProp>
+            <boolProp name="HTTPSampler.follow_redirects">true</boolProp>
+            <boolProp name="HTTPSampler.auto_redirects">false</boolProp>
+            <boolProp name="HTTPSampler.use_keepalive">true</boolProp>
+            <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp>
+            <stringProp name="HTTPSampler.embedded_url_re"></stringProp>
+            <stringProp name="HTTPSampler.connect_timeout"></stringProp>
+            <stringProp name="HTTPSampler.response_timeout"></stringProp>
+          </HTTPSamplerProxy>
+          <hashTree>
+            <JSONPathAssertion guiclass="JSONPathAssertionGui" testclass="JSONPathAssertion" testname="JSON Assertion" enabled="true">
+              <stringProp name="JSON_PATH">$.sql</stringProp>
+              <stringProp name="EXPECTED_VALUE">SELECT \* FROM demo</stringProp>
+              <boolProp name="JSONVALIDATION">true</boolProp>
+              <boolProp name="EXPECT_NULL">false</boolProp>
+              <boolProp name="INVERT">false</boolProp>
+              <boolProp name="ISREGEX">true</boolProp>
+            </JSONPathAssertion>
+            <hashTree/>
+          </hashTree>
+          <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="API_GetRuleStatus" enabled="true">
+            <boolProp name="HTTPSampler.postBodyRaw">true</boolProp>
+            <elementProp name="HTTPsampler.Arguments" elementType="Arguments">
+              <collectionProp name="Arguments.arguments">
+                <elementProp name="" elementType="HTTPArgument">
+                  <boolProp name="HTTPArgument.always_encode">false</boolProp>
+                  <stringProp name="Argument.value"></stringProp>
+                  <stringProp name="Argument.metadata">=</stringProp>
+                </elementProp>
+              </collectionProp>
+            </elementProp>
+            <stringProp name="HTTPSampler.domain">${srv}</stringProp>
+            <stringProp name="HTTPSampler.port">${rest_port}</stringProp>
+            <stringProp name="HTTPSampler.protocol"></stringProp>
+            <stringProp name="HTTPSampler.contentEncoding"></stringProp>
+            <stringProp name="HTTPSampler.path">/rules/rule1/status</stringProp>
+            <stringProp name="HTTPSampler.method">GET</stringProp>
+            <boolProp name="HTTPSampler.follow_redirects">true</boolProp>
+            <boolProp name="HTTPSampler.auto_redirects">false</boolProp>
+            <boolProp name="HTTPSampler.use_keepalive">true</boolProp>
+            <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp>
+            <stringProp name="HTTPSampler.embedded_url_re"></stringProp>
+            <stringProp name="HTTPSampler.connect_timeout"></stringProp>
+            <stringProp name="HTTPSampler.response_timeout"></stringProp>
+          </HTTPSamplerProxy>
+          <hashTree>
+            <JSONPathAssertion guiclass="JSONPathAssertionGui" testclass="JSONPathAssertion" testname="JSON Assertion" enabled="true">
+              <stringProp name="JSON_PATH">$.source_demo_0_records_in_total</stringProp>
+              <stringProp name="EXPECTED_VALUE">0</stringProp>
+              <boolProp name="JSONVALIDATION">true</boolProp>
+              <boolProp name="EXPECT_NULL">false</boolProp>
+              <boolProp name="INVERT">false</boolProp>
+              <boolProp name="ISREGEX">false</boolProp>
+            </JSONPathAssertion>
+            <hashTree/>
+          </hashTree>
+          <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="API_DropRule" enabled="true">
+            <boolProp name="HTTPSampler.postBodyRaw">true</boolProp>
+            <elementProp name="HTTPsampler.Arguments" elementType="Arguments">
+              <collectionProp name="Arguments.arguments">
+                <elementProp name="" elementType="HTTPArgument">
+                  <boolProp name="HTTPArgument.always_encode">false</boolProp>
+                  <stringProp name="Argument.value"></stringProp>
+                  <stringProp name="Argument.metadata">=</stringProp>
+                </elementProp>
+              </collectionProp>
+            </elementProp>
+            <stringProp name="HTTPSampler.domain">${srv}</stringProp>
+            <stringProp name="HTTPSampler.port">${rest_port}</stringProp>
+            <stringProp name="HTTPSampler.protocol"></stringProp>
+            <stringProp name="HTTPSampler.contentEncoding"></stringProp>
+            <stringProp name="HTTPSampler.path">/rules/rule1</stringProp>
+            <stringProp name="HTTPSampler.method">DELETE</stringProp>
+            <boolProp name="HTTPSampler.follow_redirects">true</boolProp>
+            <boolProp name="HTTPSampler.auto_redirects">false</boolProp>
+            <boolProp name="HTTPSampler.use_keepalive">true</boolProp>
+            <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp>
+            <stringProp name="HTTPSampler.embedded_url_re"></stringProp>
+            <stringProp name="HTTPSampler.connect_timeout"></stringProp>
+            <stringProp name="HTTPSampler.response_timeout"></stringProp>
+          </HTTPSamplerProxy>
+          <hashTree>
+            <ResponseAssertion guiclass="AssertionGui" testclass="ResponseAssertion" testname="Response Assertion" enabled="true">
+              <collectionProp name="Asserion.test_strings">
+                <stringProp name="717250485">Rule rule1 is dropped.</stringProp>
+              </collectionProp>
+              <stringProp name="Assertion.custom_message"></stringProp>
+              <stringProp name="Assertion.test_field">Assertion.response_data</stringProp>
+              <boolProp name="Assertion.assume_success">false</boolProp>
+              <intProp name="Assertion.test_type">16</intProp>
+            </ResponseAssertion>
+            <hashTree/>
+          </hashTree>
+          <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="API_Drop_Stream" enabled="true">
+            <boolProp name="HTTPSampler.postBodyRaw">true</boolProp>
+            <elementProp name="HTTPsampler.Arguments" elementType="Arguments">
+              <collectionProp name="Arguments.arguments">
+                <elementProp name="" elementType="HTTPArgument">
+                  <boolProp name="HTTPArgument.always_encode">false</boolProp>
+                  <stringProp name="Argument.value"></stringProp>
+                  <stringProp name="Argument.metadata">=</stringProp>
+                </elementProp>
+              </collectionProp>
+            </elementProp>
+            <stringProp name="HTTPSampler.domain">${srv}</stringProp>
+            <stringProp name="HTTPSampler.port">${rest_port}</stringProp>
+            <stringProp name="HTTPSampler.protocol"></stringProp>
+            <stringProp name="HTTPSampler.contentEncoding"></stringProp>
+            <stringProp name="HTTPSampler.path">/streams/demo</stringProp>
+            <stringProp name="HTTPSampler.method">DELETE</stringProp>
+            <boolProp name="HTTPSampler.follow_redirects">true</boolProp>
+            <boolProp name="HTTPSampler.auto_redirects">false</boolProp>
+            <boolProp name="HTTPSampler.use_keepalive">true</boolProp>
+            <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp>
+            <stringProp name="HTTPSampler.embedded_url_re"></stringProp>
+            <stringProp name="HTTPSampler.connect_timeout"></stringProp>
+            <stringProp name="HTTPSampler.response_timeout"></stringProp>
+          </HTTPSamplerProxy>
+          <hashTree>
+            <ResponseAssertion guiclass="AssertionGui" testclass="ResponseAssertion" testname="Response Assertion" enabled="true">
+              <collectionProp name="Asserion.test_strings">
+                <stringProp name="287881319">Stream demo is dropped.</stringProp>
+              </collectionProp>
+              <stringProp name="Assertion.custom_message"></stringProp>
+              <stringProp name="Assertion.test_field">Assertion.response_data</stringProp>
+              <boolProp name="Assertion.assume_success">false</boolProp>
+              <intProp name="Assertion.test_type">16</intProp>
+            </ResponseAssertion>
+            <hashTree/>
+          </hashTree>
+          <ConstantTimer guiclass="ConstantTimerGui" testclass="ConstantTimer" testname="Constant Timer" enabled="true">
+            <stringProp name="ConstantTimer.delay">500</stringProp>
+          </ConstantTimer>
+          <hashTree/>
+        </hashTree>
+        <TransactionController guiclass="TransactionControllerGui" testclass="TransactionController" testname="CLI" enabled="true">
+          <boolProp name="TransactionController.includeTimers">false</boolProp>
+          <boolProp name="TransactionController.parent">false</boolProp>
+        </TransactionController>
+        <hashTree>
+          <SystemSampler guiclass="SystemSamplerGui" testclass="SystemSampler" testname="CLI_CreateStream" enabled="true">
+            <boolProp name="SystemSampler.checkReturnCode">false</boolProp>
+            <stringProp name="SystemSampler.expectedReturnCode">0</stringProp>
+            <stringProp name="SystemSampler.command">bin/cli</stringProp>
+            <elementProp name="SystemSampler.arguments" elementType="Arguments" guiclass="ArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true">
+              <collectionProp name="Arguments.arguments">
+                <elementProp name="" elementType="Argument">
+                  <stringProp name="Argument.name"></stringProp>
+                  <stringProp name="Argument.value">create</stringProp>
+                  <stringProp name="Argument.metadata">=</stringProp>
+                </elementProp>
+                <elementProp name="" elementType="Argument">
+                  <stringProp name="Argument.name"></stringProp>
+                  <stringProp name="Argument.value">stream</stringProp>
+                  <stringProp name="Argument.metadata">=</stringProp>
+                </elementProp>
+                <elementProp name="" elementType="Argument">
+                  <stringProp name="Argument.name"></stringProp>
+                  <stringProp name="Argument.value">demo</stringProp>
+                  <stringProp name="Argument.metadata">=</stringProp>
+                </elementProp>
+                <elementProp name="" elementType="Argument">
+                  <stringProp name="Argument.name"></stringProp>
+                  <stringProp name="Argument.value">(temperature float, humidity bigint) WITH (FORMAT=&quot;JSON&quot;, DATASOURCE=&quot;devices/+/messages&quot;)</stringProp>
+                  <stringProp name="Argument.metadata">=</stringProp>
+                </elementProp>
+              </collectionProp>
+            </elementProp>
+            <elementProp name="SystemSampler.environment" elementType="Arguments" guiclass="ArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true">
+              <collectionProp name="Arguments.arguments"/>
+            </elementProp>
+            <stringProp name="SystemSampler.directory">${k_home}</stringProp>
+          </SystemSampler>
+          <hashTree>
+            <ResponseAssertion guiclass="AssertionGui" testclass="ResponseAssertion" testname="Response Assertion" enabled="true">
+              <collectionProp name="Asserion.test_strings">
+                <stringProp name="-1754954177">Stream demo is created.</stringProp>
+              </collectionProp>
+              <stringProp name="Assertion.custom_message"></stringProp>
+              <stringProp name="Assertion.test_field">Assertion.response_data</stringProp>
+              <boolProp name="Assertion.assume_success">false</boolProp>
+              <intProp name="Assertion.test_type">16</intProp>
+            </ResponseAssertion>
+            <hashTree/>
+          </hashTree>
+          <SystemSampler guiclass="SystemSamplerGui" testclass="SystemSampler" testname="CLI_CreateRule" enabled="true">
+            <boolProp name="SystemSampler.checkReturnCode">false</boolProp>
+            <stringProp name="SystemSampler.expectedReturnCode">0</stringProp>
+            <stringProp name="SystemSampler.command">bin/cli</stringProp>
+            <elementProp name="SystemSampler.arguments" elementType="Arguments" guiclass="ArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true">
+              <collectionProp name="Arguments.arguments">
+                <elementProp name="" elementType="Argument">
+                  <stringProp name="Argument.name"></stringProp>
+                  <stringProp name="Argument.value">create</stringProp>
+                  <stringProp name="Argument.metadata">=</stringProp>
+                </elementProp>
+                <elementProp name="" elementType="Argument">
+                  <stringProp name="Argument.name"></stringProp>
+                  <stringProp name="Argument.value">rule</stringProp>
+                  <stringProp name="Argument.metadata">=</stringProp>
+                </elementProp>
+                <elementProp name="" elementType="Argument">
+                  <stringProp name="Argument.name"></stringProp>
+                  <stringProp name="Argument.value">rule1</stringProp>
+                  <stringProp name="Argument.metadata">=</stringProp>
+                </elementProp>
+                <elementProp name="" elementType="Argument">
+                  <stringProp name="Argument.name"></stringProp>
+                  <stringProp name="Argument.value">-f</stringProp>
+                  <stringProp name="Argument.metadata">=</stringProp>
+                </elementProp>
+                <elementProp name="" elementType="Argument">
+                  <stringProp name="Argument.name"></stringProp>
+                  <stringProp name="Argument.value">${fvt}/fvt_scripts/rule1.txt</stringProp>
+                  <stringProp name="Argument.metadata">=</stringProp>
+                </elementProp>
+              </collectionProp>
+            </elementProp>
+            <elementProp name="SystemSampler.environment" elementType="Arguments" guiclass="ArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true">
+              <collectionProp name="Arguments.arguments"/>
+            </elementProp>
+            <stringProp name="SystemSampler.directory">${k_home}</stringProp>
+          </SystemSampler>
+          <hashTree>
+            <ResponseAssertion guiclass="AssertionGui" testclass="ResponseAssertion" testname="Response Assertion" enabled="true">
+              <collectionProp name="Asserion.test_strings">
+                <stringProp name="-2022196798">Rule rule1 was created</stringProp>
+              </collectionProp>
+              <stringProp name="Assertion.custom_message"></stringProp>
+              <stringProp name="Assertion.test_field">Assertion.response_data</stringProp>
+              <boolProp name="Assertion.assume_success">false</boolProp>
+              <intProp name="Assertion.test_type">16</intProp>
+            </ResponseAssertion>
+            <hashTree/>
+          </hashTree>
+          <SystemSampler guiclass="SystemSamplerGui" testclass="SystemSampler" testname="CLI_ShowRules" enabled="true">
+            <boolProp name="SystemSampler.checkReturnCode">false</boolProp>
+            <stringProp name="SystemSampler.expectedReturnCode">0</stringProp>
+            <stringProp name="SystemSampler.command">bin/cli</stringProp>
+            <elementProp name="SystemSampler.arguments" elementType="Arguments" guiclass="ArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true">
+              <collectionProp name="Arguments.arguments">
+                <elementProp name="" elementType="Argument">
+                  <stringProp name="Argument.name"></stringProp>
+                  <stringProp name="Argument.value">show</stringProp>
+                  <stringProp name="Argument.metadata">=</stringProp>
+                </elementProp>
+                <elementProp name="" elementType="Argument">
+                  <stringProp name="Argument.name"></stringProp>
+                  <stringProp name="Argument.value">rules</stringProp>
+                  <stringProp name="Argument.metadata">=</stringProp>
+                </elementProp>
+              </collectionProp>
+            </elementProp>
+            <elementProp name="SystemSampler.environment" elementType="Arguments" guiclass="ArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true">
+              <collectionProp name="Arguments.arguments"/>
+            </elementProp>
+            <stringProp name="SystemSampler.directory">${k_home}</stringProp>
+          </SystemSampler>
+          <hashTree>
+            <ResponseAssertion guiclass="AssertionGui" testclass="ResponseAssertion" testname="Response Assertion" enabled="true">
+              <collectionProp name="Asserion.test_strings">
+                <stringProp name="108873909">rule1</stringProp>
+              </collectionProp>
+              <stringProp name="Assertion.custom_message"></stringProp>
+              <stringProp name="Assertion.test_field">Assertion.response_data</stringProp>
+              <boolProp name="Assertion.assume_success">false</boolProp>
+              <intProp name="Assertion.test_type">16</intProp>
+            </ResponseAssertion>
+            <hashTree/>
+          </hashTree>
+          <SystemSampler guiclass="SystemSamplerGui" testclass="SystemSampler" testname="CLI_DescribeRule" enabled="true">
+            <boolProp name="SystemSampler.checkReturnCode">false</boolProp>
+            <stringProp name="SystemSampler.expectedReturnCode">0</stringProp>
+            <stringProp name="SystemSampler.command">bin/cli</stringProp>
+            <elementProp name="SystemSampler.arguments" elementType="Arguments" guiclass="ArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true">
+              <collectionProp name="Arguments.arguments">
+                <elementProp name="" elementType="Argument">
+                  <stringProp name="Argument.name"></stringProp>
+                  <stringProp name="Argument.value">describe</stringProp>
+                  <stringProp name="Argument.metadata">=</stringProp>
+                </elementProp>
+                <elementProp name="" elementType="Argument">
+                  <stringProp name="Argument.name"></stringProp>
+                  <stringProp name="Argument.value">rule</stringProp>
+                  <stringProp name="Argument.metadata">=</stringProp>
+                </elementProp>
+                <elementProp name="" elementType="Argument">
+                  <stringProp name="Argument.name"></stringProp>
+                  <stringProp name="Argument.value">rule1</stringProp>
+                  <stringProp name="Argument.metadata">=</stringProp>
+                </elementProp>
+              </collectionProp>
+            </elementProp>
+            <elementProp name="SystemSampler.environment" elementType="Arguments" guiclass="ArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true">
+              <collectionProp name="Arguments.arguments"/>
+            </elementProp>
+            <stringProp name="SystemSampler.directory">${k_home}</stringProp>
+          </SystemSampler>
+          <hashTree>
+            <ResponseAssertion guiclass="AssertionGui" testclass="ResponseAssertion" testname="Response Assertion" enabled="true">
+              <collectionProp name="Asserion.test_strings">
+                <stringProp name="304363919">devices/result</stringProp>
+              </collectionProp>
+              <stringProp name="Assertion.custom_message"></stringProp>
+              <stringProp name="Assertion.test_field">Assertion.response_data</stringProp>
+              <boolProp name="Assertion.assume_success">false</boolProp>
+              <intProp name="Assertion.test_type">16</intProp>
+            </ResponseAssertion>
+            <hashTree/>
+          </hashTree>
+          <SystemSampler guiclass="SystemSamplerGui" testclass="SystemSampler" testname="CLI_GetRuleStatus" enabled="true">
+            <boolProp name="SystemSampler.checkReturnCode">false</boolProp>
+            <stringProp name="SystemSampler.expectedReturnCode">0</stringProp>
+            <stringProp name="SystemSampler.command">bin/cli</stringProp>
+            <elementProp name="SystemSampler.arguments" elementType="Arguments" guiclass="ArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true">
+              <collectionProp name="Arguments.arguments">
+                <elementProp name="" elementType="Argument">
+                  <stringProp name="Argument.name"></stringProp>
+                  <stringProp name="Argument.value">getstatus</stringProp>
+                  <stringProp name="Argument.metadata">=</stringProp>
+                </elementProp>
+                <elementProp name="" elementType="Argument">
+                  <stringProp name="Argument.name"></stringProp>
+                  <stringProp name="Argument.value">rule</stringProp>
+                  <stringProp name="Argument.metadata">=</stringProp>
+                </elementProp>
+                <elementProp name="" elementType="Argument">
+                  <stringProp name="Argument.name"></stringProp>
+                  <stringProp name="Argument.value">rule1</stringProp>
+                  <stringProp name="Argument.metadata">=</stringProp>
+                </elementProp>
+              </collectionProp>
+            </elementProp>
+            <elementProp name="SystemSampler.environment" elementType="Arguments" guiclass="ArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true">
+              <collectionProp name="Arguments.arguments"/>
+            </elementProp>
+            <stringProp name="SystemSampler.directory">${k_home}</stringProp>
+          </SystemSampler>
+          <hashTree>
+            <ResponseAssertion guiclass="AssertionGui" testclass="ResponseAssertion" testname="Response Assertion" enabled="true">
+              <collectionProp name="Asserion.test_strings">
+                <stringProp name="679259374">source_demo_0_records_in_total</stringProp>
+              </collectionProp>
+              <stringProp name="Assertion.custom_message"></stringProp>
+              <stringProp name="Assertion.test_field">Assertion.response_data</stringProp>
+              <boolProp name="Assertion.assume_success">false</boolProp>
+              <intProp name="Assertion.test_type">16</intProp>
+            </ResponseAssertion>
+            <hashTree/>
+          </hashTree>
+          <SystemSampler guiclass="SystemSamplerGui" testclass="SystemSampler" testname="CLI_DropRule" enabled="true">
+            <boolProp name="SystemSampler.checkReturnCode">false</boolProp>
+            <stringProp name="SystemSampler.expectedReturnCode">0</stringProp>
+            <stringProp name="SystemSampler.command">bin/cli</stringProp>
+            <elementProp name="SystemSampler.arguments" elementType="Arguments" guiclass="ArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true">
+              <collectionProp name="Arguments.arguments">
+                <elementProp name="" elementType="Argument">
+                  <stringProp name="Argument.name"></stringProp>
+                  <stringProp name="Argument.value">drop</stringProp>
+                  <stringProp name="Argument.metadata">=</stringProp>
+                </elementProp>
+                <elementProp name="" elementType="Argument">
+                  <stringProp name="Argument.name"></stringProp>
+                  <stringProp name="Argument.value">rule</stringProp>
+                  <stringProp name="Argument.metadata">=</stringProp>
+                </elementProp>
+                <elementProp name="" elementType="Argument">
+                  <stringProp name="Argument.name"></stringProp>
+                  <stringProp name="Argument.value">rule1</stringProp>
+                  <stringProp name="Argument.metadata">=</stringProp>
+                </elementProp>
+              </collectionProp>
+            </elementProp>
+            <elementProp name="SystemSampler.environment" elementType="Arguments" guiclass="ArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true">
+              <collectionProp name="Arguments.arguments"/>
+            </elementProp>
+            <stringProp name="SystemSampler.directory">${k_home}</stringProp>
+          </SystemSampler>
+          <hashTree>
+            <ResponseAssertion guiclass="AssertionGui" testclass="ResponseAssertion" testname="Response Assertion" enabled="true">
+              <collectionProp name="Asserion.test_strings">
+                <stringProp name="717250485">Rule rule1 is dropped.</stringProp>
+              </collectionProp>
+              <stringProp name="Assertion.custom_message"></stringProp>
+              <stringProp name="Assertion.test_field">Assertion.response_data</stringProp>
+              <boolProp name="Assertion.assume_success">false</boolProp>
+              <intProp name="Assertion.test_type">16</intProp>
+            </ResponseAssertion>
+            <hashTree/>
+          </hashTree>
+          <SystemSampler guiclass="SystemSamplerGui" testclass="SystemSampler" testname="CLI_DropStream" enabled="true">
+            <boolProp name="SystemSampler.checkReturnCode">false</boolProp>
+            <stringProp name="SystemSampler.expectedReturnCode">0</stringProp>
+            <stringProp name="SystemSampler.command">bin/cli</stringProp>
+            <elementProp name="SystemSampler.arguments" elementType="Arguments" guiclass="ArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true">
+              <collectionProp name="Arguments.arguments">
+                <elementProp name="" elementType="Argument">
+                  <stringProp name="Argument.name"></stringProp>
+                  <stringProp name="Argument.value">drop</stringProp>
+                  <stringProp name="Argument.metadata">=</stringProp>
+                </elementProp>
+                <elementProp name="" elementType="Argument">
+                  <stringProp name="Argument.name"></stringProp>
+                  <stringProp name="Argument.value">stream</stringProp>
+                  <stringProp name="Argument.metadata">=</stringProp>
+                </elementProp>
+                <elementProp name="" elementType="Argument">
+                  <stringProp name="Argument.name"></stringProp>
+                  <stringProp name="Argument.value">demo</stringProp>
+                  <stringProp name="Argument.metadata">=</stringProp>
+                </elementProp>
+              </collectionProp>
+            </elementProp>
+            <elementProp name="SystemSampler.environment" elementType="Arguments" guiclass="ArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true">
+              <collectionProp name="Arguments.arguments"/>
+            </elementProp>
+            <stringProp name="SystemSampler.directory">${k_home}</stringProp>
+          </SystemSampler>
+          <hashTree>
+            <ResponseAssertion guiclass="AssertionGui" testclass="ResponseAssertion" testname="Response Assertion" enabled="true">
+              <collectionProp name="Asserion.test_strings">
+                <stringProp name="287881319">Stream demo is dropped.</stringProp>
+              </collectionProp>
+              <stringProp name="Assertion.custom_message"></stringProp>
+              <stringProp name="Assertion.test_field">Assertion.response_data</stringProp>
+              <boolProp name="Assertion.assume_success">false</boolProp>
+              <intProp name="Assertion.test_type">16</intProp>
+            </ResponseAssertion>
+            <hashTree/>
+          </hashTree>
+          <ConstantTimer guiclass="ConstantTimerGui" testclass="ConstantTimer" testname="Constant Timer" enabled="true">
+            <stringProp name="ConstantTimer.delay">500</stringProp>
+          </ConstantTimer>
+          <hashTree/>
+        </hashTree>
+      </hashTree>
+      <ResultCollector guiclass="ViewResultsFullVisualizer" testclass="ResultCollector" testname="View Results Tree" enabled="true">
+        <boolProp name="ResultCollector.error_logging">false</boolProp>
+        <objProp>
+          <name>saveConfig</name>
+          <value class="SampleSaveConfiguration">
+            <time>true</time>
+            <latency>true</latency>
+            <timestamp>true</timestamp>
+            <success>true</success>
+            <label>true</label>
+            <code>true</code>
+            <message>true</message>
+            <threadName>true</threadName>
+            <dataType>true</dataType>
+            <encoding>false</encoding>
+            <assertions>true</assertions>
+            <subresults>true</subresults>
+            <responseData>false</responseData>
+            <samplerData>false</samplerData>
+            <xml>false</xml>
+            <fieldNames>true</fieldNames>
+            <responseHeaders>false</responseHeaders>
+            <requestHeaders>false</requestHeaders>
+            <responseDataOnError>false</responseDataOnError>
+            <saveAssertionResultsFailureMessage>true</saveAssertionResultsFailureMessage>
+            <assertionsResultsToSave>0</assertionsResultsToSave>
+            <bytes>true</bytes>
+            <sentBytes>true</sentBytes>
+            <threadCounts>true</threadCounts>
+            <idleTime>true</idleTime>
+            <connectTime>true</connectTime>
+          </value>
+        </objProp>
+        <stringProp name="filename"></stringProp>
+      </ResultCollector>
+      <hashTree/>
+    </hashTree>
+  </hashTree>
+</jmeterTestPlan>

+ 48 - 0
fvt_scripts/run_jmeter.sh

@@ -0,0 +1,48 @@
+#!/bin/bash
+
+function downloadjar
+{
+  if [ ! -f $1 ];then
+    wget -O $1 $2
+  else
+    echo "Already downloaded $1."
+  fi
+}
+
+downloadjar "/opt/jmeter/lib/json-lib-2.4-jdk15.jar" https://repo1.maven.org/maven2/net/sf/json-lib/json-lib/2.4/json-lib-2.4-jdk15.jar
+downloadjar "/opt/jmeter/lib/commons-beanutils-1.8.0.jar" https://repo1.maven.org/maven2/commons-beanutils/commons-beanutils/1.8.0/commons-beanutils-1.8.0.jar
+downloadjar "/opt/jmeter/lib/commons-collections-3.2.1.jar" https://repo1.maven.org/maven2/commons-collections/commons-collections/3.2.1/commons-collections-3.2.1.jar
+downloadjar "/opt/jmeter/lib/commons-lang-2.5.jar" https://repo1.maven.org/maven2/commons-lang/commons-lang/2.5/commons-lang-2.5.jar
+downloadjar "/opt/jmeter/lib/commons-logging-1.1.1.jar" https://repo1.maven.org/maven2/commons-logging/commons-logging/1.1.1/commons-logging-1.1.1.jar
+downloadjar "/opt/jmeter/lib/ezmorph-1.0.6.jar" https://repo1.maven.org/maven2/net/sf/ezmorph/ezmorph/1.0.6/ezmorph-1.0.6.jar
+
+ver=`git describe --tags --always`
+os=`uname -s | tr "[A-Z]" "[a-z]"`
+base_dir=_build/kuiper-"$ver"-"$os"-x86_64
+fvt_dir=`pwd`
+
+rm -rf jmeter_logs
+
+/opt/jmeter/bin/jmeter.sh -Jjmeter.save.saveservice.output_format=xml -n -t fvt_scripts/streams_test.jmx -Dbase="$base_dir" -l jmeter_logs/stream_test.jtl
+echo -e "---------------------------------------------\n"
+
+/opt/jmeter/bin/jmeter.sh -Jjmeter.save.saveservice.output_format=xml -n -t fvt_scripts/rule_test.jmx -Dbase="$base_dir" -Dfvt="$fvt_dir" -l jmeter_logs/rule_test.jtl
+echo -e "---------------------------------------------\n"
+
+/opt/jmeter/bin/jmeter.sh -Jjmeter.save.saveservice.output_format=xml -n -t fvt_scripts/select_all_rule.jmx -l jmeter_logs/select_all_rule.jtl
+echo -e "---------------------------------------------\n"
+
+/opt/jmeter/bin/jmeter.sh -Jjmeter.save.saveservice.output_format=xml -n -t fvt_scripts/select_condition_rule.jmx -l jmeter_logs/select_condition_rule.jtl
+echo -e "---------------------------------------------\n"
+
+/opt/jmeter/bin/jmeter.sh -Jjmeter.save.saveservice.output_format=xml -n -t fvt_scripts/select_aggr_rule.jmx -l jmeter_logs/select_aggr_rule.jtl
+echo -e "---------------------------------------------\n"
+
+/opt/jmeter/bin/jmeter.sh -Jjmeter.save.saveservice.output_format=xml -n -t fvt_scripts/change_rule_status.jmx -l jmeter_logs/change_rule_status.jtl
+echo -e "---------------------------------------------\n"
+
+/opt/jmeter/bin/jmeter.sh -Jjmeter.save.saveservice.output_format=xml -n -t fvt_scripts/change_stream_rule.jmx -l jmeter_logs/change_stream_rule.jtl
+echo -e "---------------------------------------------\n"
+
+/opt/jmeter/bin/jmeter.sh -Jjmeter.save.saveservice.output_format=xml -n -t fvt_scripts/select_aggr_rule_order.jmx -l jmeter_logs/select_aggr_rule_order.jtl
+echo -e "---------------------------------------------\n"

+ 515 - 0
fvt_scripts/select_aggr_rule.jmx

@@ -0,0 +1,515 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<jmeterTestPlan version="1.2" properties="4.0" jmeter="4.0 r1823414">
+  <hashTree>
+    <TestPlan guiclass="TestPlanGui" testclass="TestPlan" testname="Test Plan" enabled="true">
+      <stringProp name="TestPlan.comments"></stringProp>
+      <boolProp name="TestPlan.functional_mode">false</boolProp>
+      <boolProp name="TestPlan.tearDown_on_shutdown">true</boolProp>
+      <boolProp name="TestPlan.serialize_threadgroups">false</boolProp>
+      <elementProp name="TestPlan.user_defined_variables" elementType="Arguments" guiclass="ArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true">
+        <collectionProp name="Arguments.arguments"/>
+      </elementProp>
+      <stringProp name="TestPlan.user_define_classpath"></stringProp>
+    </TestPlan>
+    <hashTree>
+      <Arguments guiclass="ArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true">
+        <collectionProp name="Arguments.arguments">
+          <elementProp name="srv" elementType="Argument">
+            <stringProp name="Argument.name">srv</stringProp>
+            <stringProp name="Argument.value">127.0.0.1</stringProp>
+            <stringProp name="Argument.metadata">=</stringProp>
+          </elementProp>
+          <elementProp name="rest_port" elementType="Argument">
+            <stringProp name="Argument.name">rest_port</stringProp>
+            <stringProp name="Argument.value">9081</stringProp>
+            <stringProp name="Argument.metadata">=</stringProp>
+          </elementProp>
+          <elementProp name="mqtt_srv" elementType="Argument">
+            <stringProp name="Argument.name">mqtt_srv</stringProp>
+            <stringProp name="Argument.value">127.0.0.1</stringProp>
+            <stringProp name="Argument.metadata">=</stringProp>
+          </elementProp>
+        </collectionProp>
+      </Arguments>
+      <hashTree/>
+      <ThreadGroup guiclass="ThreadGroupGui" testclass="ThreadGroup" testname="Rules" enabled="true">
+        <stringProp name="ThreadGroup.on_sample_error">continue</stringProp>
+        <elementProp name="ThreadGroup.main_controller" elementType="LoopController" guiclass="LoopControlPanel" testclass="LoopController" testname="Loop Controller" enabled="true">
+          <boolProp name="LoopController.continue_forever">false</boolProp>
+          <stringProp name="LoopController.loops">1</stringProp>
+        </elementProp>
+        <stringProp name="ThreadGroup.num_threads">1</stringProp>
+        <stringProp name="ThreadGroup.ramp_time">1</stringProp>
+        <boolProp name="ThreadGroup.scheduler">false</boolProp>
+        <stringProp name="ThreadGroup.duration"></stringProp>
+        <stringProp name="ThreadGroup.delay"></stringProp>
+      </ThreadGroup>
+      <hashTree>
+        <ResultCollector guiclass="ViewResultsFullVisualizer" testclass="ResultCollector" testname="View Results Tree" enabled="true">
+          <boolProp name="ResultCollector.error_logging">false</boolProp>
+          <objProp>
+            <name>saveConfig</name>
+            <value class="SampleSaveConfiguration">
+              <time>true</time>
+              <latency>true</latency>
+              <timestamp>true</timestamp>
+              <success>true</success>
+              <label>true</label>
+              <code>true</code>
+              <message>true</message>
+              <threadName>true</threadName>
+              <dataType>true</dataType>
+              <encoding>false</encoding>
+              <assertions>true</assertions>
+              <subresults>true</subresults>
+              <responseData>false</responseData>
+              <samplerData>false</samplerData>
+              <xml>false</xml>
+              <fieldNames>true</fieldNames>
+              <responseHeaders>false</responseHeaders>
+              <requestHeaders>false</requestHeaders>
+              <responseDataOnError>false</responseDataOnError>
+              <saveAssertionResultsFailureMessage>true</saveAssertionResultsFailureMessage>
+              <assertionsResultsToSave>0</assertionsResultsToSave>
+              <bytes>true</bytes>
+              <sentBytes>true</sentBytes>
+              <threadCounts>true</threadCounts>
+              <idleTime>true</idleTime>
+              <connectTime>true</connectTime>
+            </value>
+          </objProp>
+          <stringProp name="filename"></stringProp>
+        </ResultCollector>
+        <hashTree/>
+        <TransactionController guiclass="TransactionControllerGui" testclass="TransactionController" testname="API" enabled="true">
+          <boolProp name="TransactionController.includeTimers">false</boolProp>
+          <boolProp name="TransactionController.parent">false</boolProp>
+        </TransactionController>
+        <hashTree>
+          <net.xmeter.samplers.ConnectSampler guiclass="net.xmeter.gui.ConnectSamplerUI" testclass="net.xmeter.samplers.ConnectSampler" testname="MQTT Connect" enabled="true">
+            <stringProp name="mqtt.server">${mqtt_srv}</stringProp>
+            <stringProp name="mqtt.port">1883</stringProp>
+            <stringProp name="mqtt.version">3.1</stringProp>
+            <stringProp name="mqtt.conn_timeout">10</stringProp>
+            <boolProp name="mqtt.private_protocol">false</boolProp>
+            <stringProp name="mqtt.listener_timeout">10</stringProp>
+            <stringProp name="mqtt.protocol">TCP</stringProp>
+            <boolProp name="mqtt.dual_ssl_authentication">false</boolProp>
+            <stringProp name="mqtt.keystore_file_path"></stringProp>
+            <stringProp name="mqtt.keystore_password"></stringProp>
+            <stringProp name="mqtt.clientcert_file_path"></stringProp>
+            <stringProp name="mqtt.clientcert_password"></stringProp>
+            <stringProp name="mqtt.user_name"></stringProp>
+            <stringProp name="mqtt.password"></stringProp>
+            <stringProp name="mqtt.client_id_prefix">conn_</stringProp>
+            <boolProp name="mqtt.client_id_suffix">true</boolProp>
+            <stringProp name="mqtt.conn_keep_alive">300</stringProp>
+            <stringProp name="mqtt.conn_attampt_max">0</stringProp>
+            <stringProp name="mqtt.reconn_attampt_max">0</stringProp>
+          </net.xmeter.samplers.ConnectSampler>
+          <hashTree/>
+          <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="API_CreateStream" enabled="true">
+            <boolProp name="HTTPSampler.postBodyRaw">true</boolProp>
+            <elementProp name="HTTPsampler.Arguments" elementType="Arguments">
+              <collectionProp name="Arguments.arguments">
+                <elementProp name="" elementType="HTTPArgument">
+                  <boolProp name="HTTPArgument.always_encode">false</boolProp>
+                  <stringProp name="Argument.value">{&#xd;
+&quot;sql&quot; : &quot;create stream demo (temperature float, humidity bigint) WITH (FORMAT=\&quot;JSON\&quot;, DATASOURCE=\&quot;devices/+/messages\&quot; )&quot;&#xd;
+}</stringProp>
+                  <stringProp name="Argument.metadata">=</stringProp>
+                </elementProp>
+              </collectionProp>
+            </elementProp>
+            <stringProp name="HTTPSampler.domain">${srv}</stringProp>
+            <stringProp name="HTTPSampler.port">${rest_port}</stringProp>
+            <stringProp name="HTTPSampler.protocol"></stringProp>
+            <stringProp name="HTTPSampler.contentEncoding"></stringProp>
+            <stringProp name="HTTPSampler.path">/streams</stringProp>
+            <stringProp name="HTTPSampler.method">POST</stringProp>
+            <boolProp name="HTTPSampler.follow_redirects">true</boolProp>
+            <boolProp name="HTTPSampler.auto_redirects">false</boolProp>
+            <boolProp name="HTTPSampler.use_keepalive">true</boolProp>
+            <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp>
+            <stringProp name="HTTPSampler.embedded_url_re"></stringProp>
+            <stringProp name="HTTPSampler.connect_timeout"></stringProp>
+            <stringProp name="HTTPSampler.response_timeout"></stringProp>
+          </HTTPSamplerProxy>
+          <hashTree>
+            <ResponseAssertion guiclass="AssertionGui" testclass="ResponseAssertion" testname="Response Assertion" enabled="true">
+              <collectionProp name="Asserion.test_strings">
+                <stringProp name="-1754954177">Stream demo is created.</stringProp>
+              </collectionProp>
+              <stringProp name="Assertion.custom_message"></stringProp>
+              <stringProp name="Assertion.test_field">Assertion.response_data</stringProp>
+              <boolProp name="Assertion.assume_success">true</boolProp>
+              <intProp name="Assertion.test_type">16</intProp>
+            </ResponseAssertion>
+            <hashTree/>
+          </hashTree>
+          <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="API_CreateRule" enabled="true">
+            <boolProp name="HTTPSampler.postBodyRaw">true</boolProp>
+            <elementProp name="HTTPsampler.Arguments" elementType="Arguments">
+              <collectionProp name="Arguments.arguments">
+                <elementProp name="" elementType="HTTPArgument">
+                  <boolProp name="HTTPArgument.always_encode">false</boolProp>
+                  <stringProp name="Argument.value">{&#xd;
+  &quot;id&quot;: &quot;rule1&quot;,&#xd;
+  &quot;sql&quot;: &quot;SELECT avg(temperature) AS t_av, max(temperature) AS t_max, min(temperature) AS t_min, COUNT(*) As t_count, split_value(mqtt(topic), \&quot;/\&quot;, 1) AS device_id FROM demo GROUP BY device_id, TUMBLINGWINDOW(ss, 5)&quot;,&#xd;
+  &quot;actions&quot;: [&#xd;
+    {&#xd;
+      &quot;mqtt&quot;: {&#xd;
+        &quot;server&quot;: &quot;tcp://${mqtt_srv}:1883&quot;,&#xd;
+        &quot;topic&quot;: &quot;devices/result&quot;,&#xd;
+        &quot;qos&quot;: 1,&#xd;
+        &quot;clientId&quot;: &quot;demo_001&quot;&#xd;
+      }&#xd;
+    }&#xd;
+  ]&#xd;
+}</stringProp>
+                  <stringProp name="Argument.metadata">=</stringProp>
+                </elementProp>
+              </collectionProp>
+            </elementProp>
+            <stringProp name="HTTPSampler.domain">${srv}</stringProp>
+            <stringProp name="HTTPSampler.port">${rest_port}</stringProp>
+            <stringProp name="HTTPSampler.protocol"></stringProp>
+            <stringProp name="HTTPSampler.contentEncoding"></stringProp>
+            <stringProp name="HTTPSampler.path">/rules</stringProp>
+            <stringProp name="HTTPSampler.method">POST</stringProp>
+            <boolProp name="HTTPSampler.follow_redirects">true</boolProp>
+            <boolProp name="HTTPSampler.auto_redirects">false</boolProp>
+            <boolProp name="HTTPSampler.use_keepalive">true</boolProp>
+            <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp>
+            <stringProp name="HTTPSampler.embedded_url_re"></stringProp>
+            <stringProp name="HTTPSampler.connect_timeout"></stringProp>
+            <stringProp name="HTTPSampler.response_timeout"></stringProp>
+          </HTTPSamplerProxy>
+          <hashTree>
+            <ResponseAssertion guiclass="AssertionGui" testclass="ResponseAssertion" testname="Response Assertion" enabled="true">
+              <collectionProp name="Asserion.test_strings">
+                <stringProp name="-2022196798">Rule rule1 was created</stringProp>
+              </collectionProp>
+              <stringProp name="Assertion.custom_message"></stringProp>
+              <stringProp name="Assertion.test_field">Assertion.response_data</stringProp>
+              <boolProp name="Assertion.assume_success">true</boolProp>
+              <intProp name="Assertion.test_type">16</intProp>
+            </ResponseAssertion>
+            <hashTree/>
+          </hashTree>
+          <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="API_GetRuleStatus" enabled="true">
+            <boolProp name="HTTPSampler.postBodyRaw">true</boolProp>
+            <elementProp name="HTTPsampler.Arguments" elementType="Arguments">
+              <collectionProp name="Arguments.arguments">
+                <elementProp name="" elementType="HTTPArgument">
+                  <boolProp name="HTTPArgument.always_encode">false</boolProp>
+                  <stringProp name="Argument.value"></stringProp>
+                  <stringProp name="Argument.metadata">=</stringProp>
+                </elementProp>
+              </collectionProp>
+            </elementProp>
+            <stringProp name="HTTPSampler.domain">${srv}</stringProp>
+            <stringProp name="HTTPSampler.port">${rest_port}</stringProp>
+            <stringProp name="HTTPSampler.protocol"></stringProp>
+            <stringProp name="HTTPSampler.contentEncoding"></stringProp>
+            <stringProp name="HTTPSampler.path">/rules/rule1/status</stringProp>
+            <stringProp name="HTTPSampler.method">GET</stringProp>
+            <boolProp name="HTTPSampler.follow_redirects">true</boolProp>
+            <boolProp name="HTTPSampler.auto_redirects">false</boolProp>
+            <boolProp name="HTTPSampler.use_keepalive">true</boolProp>
+            <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp>
+            <stringProp name="HTTPSampler.embedded_url_re"></stringProp>
+            <stringProp name="HTTPSampler.connect_timeout"></stringProp>
+            <stringProp name="HTTPSampler.response_timeout"></stringProp>
+          </HTTPSamplerProxy>
+          <hashTree>
+            <JSONPathAssertion guiclass="JSONPathAssertionGui" testclass="JSONPathAssertion" testname="JSON Assertion" enabled="true">
+              <stringProp name="JSON_PATH">$.source_demo_0_records_in_total</stringProp>
+              <stringProp name="EXPECTED_VALUE">0</stringProp>
+              <boolProp name="JSONVALIDATION">true</boolProp>
+              <boolProp name="EXPECT_NULL">false</boolProp>
+              <boolProp name="INVERT">false</boolProp>
+              <boolProp name="ISREGEX">false</boolProp>
+            </JSONPathAssertion>
+            <hashTree/>
+          </hashTree>
+          <LoopController guiclass="LoopControlPanel" testclass="LoopController" testname="loop_controller" enabled="true">
+            <boolProp name="LoopController.continue_forever">true</boolProp>
+            <stringProp name="LoopController.loops">10</stringProp>
+          </LoopController>
+          <hashTree>
+            <CSVDataSet guiclass="TestBeanGUI" testclass="CSVDataSet" testname="CSV Data Set Config" enabled="true">
+              <stringProp name="delimiter">,</stringProp>
+              <stringProp name="fileEncoding"></stringProp>
+              <stringProp name="filename">iot_data.txt</stringProp>
+              <boolProp name="ignoreFirstLine">false</boolProp>
+              <boolProp name="quotedData">false</boolProp>
+              <boolProp name="recycle">true</boolProp>
+              <stringProp name="shareMode">shareMode.thread</stringProp>
+              <boolProp name="stopThread">false</boolProp>
+              <stringProp name="variableNames">device_id,temperature,humidity</stringProp>
+            </CSVDataSet>
+            <hashTree/>
+            <net.xmeter.samplers.PubSampler guiclass="net.xmeter.gui.PubSamplerUI" testclass="net.xmeter.samplers.PubSampler" testname="MQTT Pub Sampler" enabled="true">
+              <stringProp name="mqtt.topic_name">devices/${device_id}/messages</stringProp>
+              <stringProp name="mqtt.qos_level">0</stringProp>
+              <boolProp name="mqtt.add_timestamp">false</boolProp>
+              <stringProp name="mqtt.message_type">String</stringProp>
+              <stringProp name="mqtt.message_type_fixed_length">1024</stringProp>
+              <stringProp name="mqtt.message_to_sent">{&quot;temperature&quot;: ${temperature}, &quot;humidity&quot; : ${humidity}}</stringProp>
+            </net.xmeter.samplers.PubSampler>
+            <hashTree/>
+          </hashTree>
+          <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="API_GetRuleStatus" enabled="true">
+            <boolProp name="HTTPSampler.postBodyRaw">true</boolProp>
+            <elementProp name="HTTPsampler.Arguments" elementType="Arguments">
+              <collectionProp name="Arguments.arguments">
+                <elementProp name="" elementType="HTTPArgument">
+                  <boolProp name="HTTPArgument.always_encode">false</boolProp>
+                  <stringProp name="Argument.value"></stringProp>
+                  <stringProp name="Argument.metadata">=</stringProp>
+                </elementProp>
+              </collectionProp>
+            </elementProp>
+            <stringProp name="HTTPSampler.domain">${srv}</stringProp>
+            <stringProp name="HTTPSampler.port">${rest_port}</stringProp>
+            <stringProp name="HTTPSampler.protocol"></stringProp>
+            <stringProp name="HTTPSampler.contentEncoding"></stringProp>
+            <stringProp name="HTTPSampler.path">/rules/rule1/status</stringProp>
+            <stringProp name="HTTPSampler.method">GET</stringProp>
+            <boolProp name="HTTPSampler.follow_redirects">true</boolProp>
+            <boolProp name="HTTPSampler.auto_redirects">false</boolProp>
+            <boolProp name="HTTPSampler.use_keepalive">true</boolProp>
+            <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp>
+            <stringProp name="HTTPSampler.embedded_url_re"></stringProp>
+            <stringProp name="HTTPSampler.connect_timeout"></stringProp>
+            <stringProp name="HTTPSampler.response_timeout"></stringProp>
+          </HTTPSamplerProxy>
+          <hashTree>
+            <JSONPathAssertion guiclass="JSONPathAssertionGui" testclass="JSONPathAssertion" testname="JSON Assertion" enabled="true">
+              <stringProp name="JSON_PATH">$.source_demo_0_records_in_total</stringProp>
+              <stringProp name="EXPECTED_VALUE">10</stringProp>
+              <boolProp name="JSONVALIDATION">true</boolProp>
+              <boolProp name="EXPECT_NULL">false</boolProp>
+              <boolProp name="INVERT">false</boolProp>
+              <boolProp name="ISREGEX">false</boolProp>
+            </JSONPathAssertion>
+            <hashTree/>
+            <JSONPathAssertion guiclass="JSONPathAssertionGui" testclass="JSONPathAssertion" testname="JSON Assertion" enabled="false">
+              <stringProp name="JSON_PATH">$.sink_sink_mqtt_0_records_in_total</stringProp>
+              <stringProp name="EXPECTED_VALUE">6</stringProp>
+              <boolProp name="JSONVALIDATION">true</boolProp>
+              <boolProp name="EXPECT_NULL">false</boolProp>
+              <boolProp name="INVERT">false</boolProp>
+              <boolProp name="ISREGEX">false</boolProp>
+            </JSONPathAssertion>
+            <hashTree/>
+            <ConstantTimer guiclass="ConstantTimerGui" testclass="ConstantTimer" testname="Constant Timer" enabled="true">
+              <stringProp name="ConstantTimer.delay">5000</stringProp>
+            </ConstantTimer>
+            <hashTree/>
+          </hashTree>
+          <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="API_DropRule" enabled="true">
+            <boolProp name="HTTPSampler.postBodyRaw">true</boolProp>
+            <elementProp name="HTTPsampler.Arguments" elementType="Arguments">
+              <collectionProp name="Arguments.arguments">
+                <elementProp name="" elementType="HTTPArgument">
+                  <boolProp name="HTTPArgument.always_encode">false</boolProp>
+                  <stringProp name="Argument.value"></stringProp>
+                  <stringProp name="Argument.metadata">=</stringProp>
+                </elementProp>
+              </collectionProp>
+            </elementProp>
+            <stringProp name="HTTPSampler.domain">${srv}</stringProp>
+            <stringProp name="HTTPSampler.port">${rest_port}</stringProp>
+            <stringProp name="HTTPSampler.protocol"></stringProp>
+            <stringProp name="HTTPSampler.contentEncoding"></stringProp>
+            <stringProp name="HTTPSampler.path">/rules/rule1</stringProp>
+            <stringProp name="HTTPSampler.method">DELETE</stringProp>
+            <boolProp name="HTTPSampler.follow_redirects">true</boolProp>
+            <boolProp name="HTTPSampler.auto_redirects">false</boolProp>
+            <boolProp name="HTTPSampler.use_keepalive">true</boolProp>
+            <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp>
+            <stringProp name="HTTPSampler.embedded_url_re"></stringProp>
+            <stringProp name="HTTPSampler.connect_timeout"></stringProp>
+            <stringProp name="HTTPSampler.response_timeout"></stringProp>
+          </HTTPSamplerProxy>
+          <hashTree>
+            <ResponseAssertion guiclass="AssertionGui" testclass="ResponseAssertion" testname="Response Assertion" enabled="true">
+              <collectionProp name="Asserion.test_strings">
+                <stringProp name="717250485">Rule rule1 is dropped.</stringProp>
+              </collectionProp>
+              <stringProp name="Assertion.custom_message"></stringProp>
+              <stringProp name="Assertion.test_field">Assertion.response_data</stringProp>
+              <boolProp name="Assertion.assume_success">false</boolProp>
+              <intProp name="Assertion.test_type">16</intProp>
+            </ResponseAssertion>
+            <hashTree/>
+          </hashTree>
+          <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="API_Drop_Stream" enabled="true">
+            <boolProp name="HTTPSampler.postBodyRaw">true</boolProp>
+            <elementProp name="HTTPsampler.Arguments" elementType="Arguments">
+              <collectionProp name="Arguments.arguments">
+                <elementProp name="" elementType="HTTPArgument">
+                  <boolProp name="HTTPArgument.always_encode">false</boolProp>
+                  <stringProp name="Argument.value"></stringProp>
+                  <stringProp name="Argument.metadata">=</stringProp>
+                </elementProp>
+              </collectionProp>
+            </elementProp>
+            <stringProp name="HTTPSampler.domain">${srv}</stringProp>
+            <stringProp name="HTTPSampler.port">${rest_port}</stringProp>
+            <stringProp name="HTTPSampler.protocol"></stringProp>
+            <stringProp name="HTTPSampler.contentEncoding"></stringProp>
+            <stringProp name="HTTPSampler.path">/streams/demo</stringProp>
+            <stringProp name="HTTPSampler.method">DELETE</stringProp>
+            <boolProp name="HTTPSampler.follow_redirects">true</boolProp>
+            <boolProp name="HTTPSampler.auto_redirects">false</boolProp>
+            <boolProp name="HTTPSampler.use_keepalive">true</boolProp>
+            <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp>
+            <stringProp name="HTTPSampler.embedded_url_re"></stringProp>
+            <stringProp name="HTTPSampler.connect_timeout"></stringProp>
+            <stringProp name="HTTPSampler.response_timeout"></stringProp>
+          </HTTPSamplerProxy>
+          <hashTree>
+            <ResponseAssertion guiclass="AssertionGui" testclass="ResponseAssertion" testname="Response Assertion" enabled="true">
+              <collectionProp name="Asserion.test_strings">
+                <stringProp name="287881319">Stream demo is dropped.</stringProp>
+              </collectionProp>
+              <stringProp name="Assertion.custom_message"></stringProp>
+              <stringProp name="Assertion.test_field">Assertion.response_data</stringProp>
+              <boolProp name="Assertion.assume_success">false</boolProp>
+              <intProp name="Assertion.test_type">16</intProp>
+            </ResponseAssertion>
+            <hashTree/>
+          </hashTree>
+          <ConstantTimer guiclass="ConstantTimerGui" testclass="ConstantTimer" testname="Constant Timer" enabled="true">
+            <stringProp name="ConstantTimer.delay">500</stringProp>
+          </ConstantTimer>
+          <hashTree/>
+        </hashTree>
+      </hashTree>
+      <ThreadGroup guiclass="ThreadGroupGui" testclass="ThreadGroup" testname="Result" enabled="true">
+        <stringProp name="ThreadGroup.on_sample_error">continue</stringProp>
+        <elementProp name="ThreadGroup.main_controller" elementType="LoopController" guiclass="LoopControlPanel" testclass="LoopController" testname="Loop Controller" enabled="true">
+          <boolProp name="LoopController.continue_forever">false</boolProp>
+          <stringProp name="LoopController.loops">1</stringProp>
+        </elementProp>
+        <stringProp name="ThreadGroup.num_threads">1</stringProp>
+        <stringProp name="ThreadGroup.ramp_time">1</stringProp>
+        <boolProp name="ThreadGroup.scheduler">false</boolProp>
+        <stringProp name="ThreadGroup.duration"></stringProp>
+        <stringProp name="ThreadGroup.delay"></stringProp>
+      </ThreadGroup>
+      <hashTree>
+        <ResultCollector guiclass="ViewResultsFullVisualizer" testclass="ResultCollector" testname="View Results Tree" enabled="true">
+          <boolProp name="ResultCollector.error_logging">false</boolProp>
+          <objProp>
+            <name>saveConfig</name>
+            <value class="SampleSaveConfiguration">
+              <time>true</time>
+              <latency>true</latency>
+              <timestamp>true</timestamp>
+              <success>true</success>
+              <label>true</label>
+              <code>true</code>
+              <message>true</message>
+              <threadName>true</threadName>
+              <dataType>true</dataType>
+              <encoding>false</encoding>
+              <assertions>true</assertions>
+              <subresults>true</subresults>
+              <responseData>false</responseData>
+              <samplerData>false</samplerData>
+              <xml>false</xml>
+              <fieldNames>true</fieldNames>
+              <responseHeaders>false</responseHeaders>
+              <requestHeaders>false</requestHeaders>
+              <responseDataOnError>false</responseDataOnError>
+              <saveAssertionResultsFailureMessage>true</saveAssertionResultsFailureMessage>
+              <assertionsResultsToSave>0</assertionsResultsToSave>
+              <bytes>true</bytes>
+              <sentBytes>true</sentBytes>
+              <threadCounts>true</threadCounts>
+              <idleTime>true</idleTime>
+              <connectTime>true</connectTime>
+            </value>
+          </objProp>
+          <stringProp name="filename"></stringProp>
+        </ResultCollector>
+        <hashTree/>
+        <OnceOnlyController guiclass="OnceOnlyControllerGui" testclass="OnceOnlyController" testname="Once Only Controller" enabled="true"/>
+        <hashTree>
+          <net.xmeter.samplers.ConnectSampler guiclass="net.xmeter.gui.ConnectSamplerUI" testclass="net.xmeter.samplers.ConnectSampler" testname="MQTT Connect" enabled="true">
+            <stringProp name="mqtt.server">${mqtt_srv}</stringProp>
+            <stringProp name="mqtt.port">1883</stringProp>
+            <stringProp name="mqtt.version">3.1</stringProp>
+            <stringProp name="mqtt.conn_timeout">10</stringProp>
+            <boolProp name="mqtt.private_protocol">false</boolProp>
+            <stringProp name="mqtt.listener_timeout">10</stringProp>
+            <stringProp name="mqtt.protocol">TCP</stringProp>
+            <boolProp name="mqtt.dual_ssl_authentication">false</boolProp>
+            <stringProp name="mqtt.keystore_file_path"></stringProp>
+            <stringProp name="mqtt.keystore_password"></stringProp>
+            <stringProp name="mqtt.clientcert_file_path"></stringProp>
+            <stringProp name="mqtt.clientcert_password"></stringProp>
+            <stringProp name="mqtt.user_name"></stringProp>
+            <stringProp name="mqtt.password"></stringProp>
+            <stringProp name="mqtt.client_id_prefix">conn_</stringProp>
+            <boolProp name="mqtt.client_id_suffix">true</boolProp>
+            <stringProp name="mqtt.conn_keep_alive">300</stringProp>
+            <stringProp name="mqtt.conn_attampt_max">0</stringProp>
+            <stringProp name="mqtt.reconn_attampt_max">0</stringProp>
+          </net.xmeter.samplers.ConnectSampler>
+          <hashTree/>
+        </hashTree>
+        <net.xmeter.samplers.SubSampler guiclass="net.xmeter.gui.SubSamplerUI" testclass="net.xmeter.samplers.SubSampler" testname="AnalysisResult" enabled="true">
+          <stringProp name="mqtt.topic_name">devices/result</stringProp>
+          <stringProp name="mqtt.qos_level">0</stringProp>
+          <boolProp name="mqtt.add_timestamp">false</boolProp>
+          <boolProp name="mqtt.debug_response">true</boolProp>
+          <stringProp name="mqtt.sample_condition">specified elapsed time (ms)</stringProp>
+          <stringProp name="mqtt.sample_condition_value">15000</stringProp>
+        </net.xmeter.samplers.SubSampler>
+        <hashTree>
+          <BeanShellAssertion guiclass="BeanShellAssertionGui" testclass="BeanShellAssertion" testname="BeanShell Assertion" enabled="true">
+            <stringProp name="BeanShellAssertion.query">import net.sf.json.JSONArray;
+import net.sf.json.JSONObject;
+
+String response = SampleResult.getResponseDataAsString();
+
+String[] arr =  response.split(&quot;\n&quot;);
+for(int i = 0; i &lt; arr.length; i++) {
+	JSONArray jsonArr = JSONArray.fromObject(arr[i]);
+	for(int j = 0; j &lt; jsonArr.size(); j++) {
+		
+		String deviceid = jsonArr.getJSONObject(j).getString(&quot;device_id&quot;);
+		int count = jsonArr.getJSONObject(j).getInt(&quot;t_count&quot;);
+
+		String oldCount = vars.get(deviceid);
+		if(oldCount == null) {
+			vars.put(deviceid, String.valueOf(count));
+		} else {
+			int c = Integer.parseInt(oldCount);
+			c += count;
+			vars.put(deviceid, String.valueOf(c));
+		}
+	}
+}
+
+String d1 = vars.get(&quot;1&quot;);
+String d2 = vars.get(&quot;2&quot;);
+if(d1 == null || (!&quot;5&quot;.equals(d1)) || d2 == null || (!&quot;5&quot;.equals(d2))) {
+	Failure = true;
+	FailureMessage = &quot;The analyis result is not correct!&quot;;
+}</stringProp>
+            <stringProp name="BeanShellAssertion.filename"></stringProp>
+            <stringProp name="BeanShellAssertion.parameters"></stringProp>
+            <boolProp name="BeanShellAssertion.resetInterpreter">false</boolProp>
+          </BeanShellAssertion>
+          <hashTree/>
+        </hashTree>
+      </hashTree>
+    </hashTree>
+  </hashTree>
+</jmeterTestPlan>

+ 520 - 0
fvt_scripts/select_aggr_rule_order.jmx

@@ -0,0 +1,520 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<jmeterTestPlan version="1.2" properties="4.0" jmeter="4.0 r1823414">
+  <hashTree>
+    <TestPlan guiclass="TestPlanGui" testclass="TestPlan" testname="Test Plan" enabled="true">
+      <stringProp name="TestPlan.comments"></stringProp>
+      <boolProp name="TestPlan.functional_mode">false</boolProp>
+      <boolProp name="TestPlan.tearDown_on_shutdown">true</boolProp>
+      <boolProp name="TestPlan.serialize_threadgroups">false</boolProp>
+      <elementProp name="TestPlan.user_defined_variables" elementType="Arguments" guiclass="ArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true">
+        <collectionProp name="Arguments.arguments"/>
+      </elementProp>
+      <stringProp name="TestPlan.user_define_classpath"></stringProp>
+    </TestPlan>
+    <hashTree>
+      <Arguments guiclass="ArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true">
+        <collectionProp name="Arguments.arguments">
+          <elementProp name="srv" elementType="Argument">
+            <stringProp name="Argument.name">srv</stringProp>
+            <stringProp name="Argument.value">127.0.0.1</stringProp>
+            <stringProp name="Argument.metadata">=</stringProp>
+          </elementProp>
+          <elementProp name="rest_port" elementType="Argument">
+            <stringProp name="Argument.name">rest_port</stringProp>
+            <stringProp name="Argument.value">9081</stringProp>
+            <stringProp name="Argument.metadata">=</stringProp>
+          </elementProp>
+          <elementProp name="k_home" elementType="Argument">
+            <stringProp name="Argument.name">k_home</stringProp>
+            <stringProp name="Argument.value">${__property(base,,)}</stringProp>
+            <stringProp name="Argument.metadata">=</stringProp>
+          </elementProp>
+          <elementProp name="fvt" elementType="Argument">
+            <stringProp name="Argument.name">fvt</stringProp>
+            <stringProp name="Argument.value">${__property(fvt,,)}</stringProp>
+            <stringProp name="Argument.metadata">=</stringProp>
+          </elementProp>
+          <elementProp name="mqtt_srv" elementType="Argument">
+            <stringProp name="Argument.name">mqtt_srv</stringProp>
+            <stringProp name="Argument.value">127.0.0.1</stringProp>
+            <stringProp name="Argument.metadata">=</stringProp>
+          </elementProp>
+        </collectionProp>
+      </Arguments>
+      <hashTree/>
+      <ThreadGroup guiclass="ThreadGroupGui" testclass="ThreadGroup" testname="Rules" enabled="true">
+        <stringProp name="ThreadGroup.on_sample_error">continue</stringProp>
+        <elementProp name="ThreadGroup.main_controller" elementType="LoopController" guiclass="LoopControlPanel" testclass="LoopController" testname="Loop Controller" enabled="true">
+          <boolProp name="LoopController.continue_forever">false</boolProp>
+          <stringProp name="LoopController.loops">1</stringProp>
+        </elementProp>
+        <stringProp name="ThreadGroup.num_threads">1</stringProp>
+        <stringProp name="ThreadGroup.ramp_time">1</stringProp>
+        <boolProp name="ThreadGroup.scheduler">false</boolProp>
+        <stringProp name="ThreadGroup.duration"></stringProp>
+        <stringProp name="ThreadGroup.delay"></stringProp>
+      </ThreadGroup>
+      <hashTree>
+        <ResultCollector guiclass="ViewResultsFullVisualizer" testclass="ResultCollector" testname="View Results Tree" enabled="true">
+          <boolProp name="ResultCollector.error_logging">false</boolProp>
+          <objProp>
+            <name>saveConfig</name>
+            <value class="SampleSaveConfiguration">
+              <time>true</time>
+              <latency>true</latency>
+              <timestamp>true</timestamp>
+              <success>true</success>
+              <label>true</label>
+              <code>true</code>
+              <message>true</message>
+              <threadName>true</threadName>
+              <dataType>true</dataType>
+              <encoding>false</encoding>
+              <assertions>true</assertions>
+              <subresults>true</subresults>
+              <responseData>false</responseData>
+              <samplerData>false</samplerData>
+              <xml>false</xml>
+              <fieldNames>true</fieldNames>
+              <responseHeaders>false</responseHeaders>
+              <requestHeaders>false</requestHeaders>
+              <responseDataOnError>false</responseDataOnError>
+              <saveAssertionResultsFailureMessage>true</saveAssertionResultsFailureMessage>
+              <assertionsResultsToSave>0</assertionsResultsToSave>
+              <bytes>true</bytes>
+              <sentBytes>true</sentBytes>
+              <threadCounts>true</threadCounts>
+              <idleTime>true</idleTime>
+              <connectTime>true</connectTime>
+            </value>
+          </objProp>
+          <stringProp name="filename"></stringProp>
+        </ResultCollector>
+        <hashTree/>
+        <TransactionController guiclass="TransactionControllerGui" testclass="TransactionController" testname="API" enabled="true">
+          <boolProp name="TransactionController.includeTimers">false</boolProp>
+          <boolProp name="TransactionController.parent">false</boolProp>
+        </TransactionController>
+        <hashTree>
+          <net.xmeter.samplers.ConnectSampler guiclass="net.xmeter.gui.ConnectSamplerUI" testclass="net.xmeter.samplers.ConnectSampler" testname="MQTT Connect" enabled="true">
+            <stringProp name="mqtt.server">${mqtt_srv}</stringProp>
+            <stringProp name="mqtt.port">1883</stringProp>
+            <stringProp name="mqtt.version">3.1</stringProp>
+            <stringProp name="mqtt.conn_timeout">10</stringProp>
+            <boolProp name="mqtt.private_protocol">false</boolProp>
+            <stringProp name="mqtt.listener_timeout">10</stringProp>
+            <stringProp name="mqtt.protocol">TCP</stringProp>
+            <boolProp name="mqtt.dual_ssl_authentication">false</boolProp>
+            <stringProp name="mqtt.keystore_file_path"></stringProp>
+            <stringProp name="mqtt.keystore_password"></stringProp>
+            <stringProp name="mqtt.clientcert_file_path"></stringProp>
+            <stringProp name="mqtt.clientcert_password"></stringProp>
+            <stringProp name="mqtt.user_name"></stringProp>
+            <stringProp name="mqtt.password"></stringProp>
+            <stringProp name="mqtt.client_id_prefix">conn_</stringProp>
+            <boolProp name="mqtt.client_id_suffix">true</boolProp>
+            <stringProp name="mqtt.conn_keep_alive">300</stringProp>
+            <stringProp name="mqtt.conn_attampt_max">0</stringProp>
+            <stringProp name="mqtt.reconn_attampt_max">0</stringProp>
+          </net.xmeter.samplers.ConnectSampler>
+          <hashTree/>
+          <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="API_CreateStream" enabled="true">
+            <boolProp name="HTTPSampler.postBodyRaw">true</boolProp>
+            <elementProp name="HTTPsampler.Arguments" elementType="Arguments">
+              <collectionProp name="Arguments.arguments">
+                <elementProp name="" elementType="HTTPArgument">
+                  <boolProp name="HTTPArgument.always_encode">false</boolProp>
+                  <stringProp name="Argument.value">{&#xd;
+&quot;sql&quot; : &quot;create stream demo (temperature float, humidity bigint) WITH (FORMAT=\&quot;JSON\&quot;, DATASOURCE=\&quot;devices/+/messages\&quot; )&quot;&#xd;
+}</stringProp>
+                  <stringProp name="Argument.metadata">=</stringProp>
+                </elementProp>
+              </collectionProp>
+            </elementProp>
+            <stringProp name="HTTPSampler.domain">${srv}</stringProp>
+            <stringProp name="HTTPSampler.port">${rest_port}</stringProp>
+            <stringProp name="HTTPSampler.protocol"></stringProp>
+            <stringProp name="HTTPSampler.contentEncoding"></stringProp>
+            <stringProp name="HTTPSampler.path">/streams</stringProp>
+            <stringProp name="HTTPSampler.method">POST</stringProp>
+            <boolProp name="HTTPSampler.follow_redirects">true</boolProp>
+            <boolProp name="HTTPSampler.auto_redirects">false</boolProp>
+            <boolProp name="HTTPSampler.use_keepalive">true</boolProp>
+            <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp>
+            <stringProp name="HTTPSampler.embedded_url_re"></stringProp>
+            <stringProp name="HTTPSampler.connect_timeout"></stringProp>
+            <stringProp name="HTTPSampler.response_timeout"></stringProp>
+          </HTTPSamplerProxy>
+          <hashTree>
+            <ResponseAssertion guiclass="AssertionGui" testclass="ResponseAssertion" testname="Response Assertion" enabled="true">
+              <collectionProp name="Asserion.test_strings">
+                <stringProp name="-1754954177">Stream demo is created.</stringProp>
+              </collectionProp>
+              <stringProp name="Assertion.custom_message"></stringProp>
+              <stringProp name="Assertion.test_field">Assertion.response_data</stringProp>
+              <boolProp name="Assertion.assume_success">true</boolProp>
+              <intProp name="Assertion.test_type">16</intProp>
+            </ResponseAssertion>
+            <hashTree/>
+          </hashTree>
+          <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="API_CreateRule" enabled="true">
+            <boolProp name="HTTPSampler.postBodyRaw">true</boolProp>
+            <elementProp name="HTTPsampler.Arguments" elementType="Arguments">
+              <collectionProp name="Arguments.arguments">
+                <elementProp name="" elementType="HTTPArgument">
+                  <boolProp name="HTTPArgument.always_encode">false</boolProp>
+                  <stringProp name="Argument.value">{&#xd;
+  &quot;id&quot;: &quot;rule1&quot;,&#xd;
+  &quot;sql&quot;: &quot;SELECT temperature, humidity, split_value(mqtt(topic), \&quot;/\&quot;, 1) AS device_id FROM demo GROUP BY TUMBLINGWINDOW(ss, 10) ORDER BY device_id DESC, temperature&quot;,&#xd;
+  &quot;actions&quot;: [&#xd;
+    {&#xd;
+      &quot;mqtt&quot;: {&#xd;
+        &quot;server&quot;: &quot;tcp://${mqtt_srv}:1883&quot;,&#xd;
+        &quot;topic&quot;: &quot;devices/result&quot;,&#xd;
+        &quot;qos&quot;: 1,&#xd;
+        &quot;clientId&quot;: &quot;demo_001&quot;&#xd;
+      }&#xd;
+    }&#xd;
+  ]&#xd;
+}</stringProp>
+                  <stringProp name="Argument.metadata">=</stringProp>
+                </elementProp>
+              </collectionProp>
+            </elementProp>
+            <stringProp name="HTTPSampler.domain">${srv}</stringProp>
+            <stringProp name="HTTPSampler.port">${rest_port}</stringProp>
+            <stringProp name="HTTPSampler.protocol"></stringProp>
+            <stringProp name="HTTPSampler.contentEncoding"></stringProp>
+            <stringProp name="HTTPSampler.path">/rules</stringProp>
+            <stringProp name="HTTPSampler.method">POST</stringProp>
+            <boolProp name="HTTPSampler.follow_redirects">true</boolProp>
+            <boolProp name="HTTPSampler.auto_redirects">false</boolProp>
+            <boolProp name="HTTPSampler.use_keepalive">true</boolProp>
+            <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp>
+            <stringProp name="HTTPSampler.embedded_url_re"></stringProp>
+            <stringProp name="HTTPSampler.connect_timeout"></stringProp>
+            <stringProp name="HTTPSampler.response_timeout"></stringProp>
+          </HTTPSamplerProxy>
+          <hashTree>
+            <ResponseAssertion guiclass="AssertionGui" testclass="ResponseAssertion" testname="Response Assertion" enabled="true">
+              <collectionProp name="Asserion.test_strings">
+                <stringProp name="-2022196798">Rule rule1 was created</stringProp>
+              </collectionProp>
+              <stringProp name="Assertion.custom_message"></stringProp>
+              <stringProp name="Assertion.test_field">Assertion.response_data</stringProp>
+              <boolProp name="Assertion.assume_success">true</boolProp>
+              <intProp name="Assertion.test_type">16</intProp>
+            </ResponseAssertion>
+            <hashTree/>
+          </hashTree>
+          <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="API_GetRuleStatus" enabled="true">
+            <boolProp name="HTTPSampler.postBodyRaw">true</boolProp>
+            <elementProp name="HTTPsampler.Arguments" elementType="Arguments">
+              <collectionProp name="Arguments.arguments">
+                <elementProp name="" elementType="HTTPArgument">
+                  <boolProp name="HTTPArgument.always_encode">false</boolProp>
+                  <stringProp name="Argument.value"></stringProp>
+                  <stringProp name="Argument.metadata">=</stringProp>
+                </elementProp>
+              </collectionProp>
+            </elementProp>
+            <stringProp name="HTTPSampler.domain">${srv}</stringProp>
+            <stringProp name="HTTPSampler.port">${rest_port}</stringProp>
+            <stringProp name="HTTPSampler.protocol"></stringProp>
+            <stringProp name="HTTPSampler.contentEncoding"></stringProp>
+            <stringProp name="HTTPSampler.path">/rules/rule1/status</stringProp>
+            <stringProp name="HTTPSampler.method">GET</stringProp>
+            <boolProp name="HTTPSampler.follow_redirects">true</boolProp>
+            <boolProp name="HTTPSampler.auto_redirects">false</boolProp>
+            <boolProp name="HTTPSampler.use_keepalive">true</boolProp>
+            <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp>
+            <stringProp name="HTTPSampler.embedded_url_re"></stringProp>
+            <stringProp name="HTTPSampler.connect_timeout"></stringProp>
+            <stringProp name="HTTPSampler.response_timeout"></stringProp>
+          </HTTPSamplerProxy>
+          <hashTree>
+            <JSONPathAssertion guiclass="JSONPathAssertionGui" testclass="JSONPathAssertion" testname="JSON Assertion" enabled="true">
+              <stringProp name="JSON_PATH">$.source_demo_0_records_in_total</stringProp>
+              <stringProp name="EXPECTED_VALUE">0</stringProp>
+              <boolProp name="JSONVALIDATION">true</boolProp>
+              <boolProp name="EXPECT_NULL">false</boolProp>
+              <boolProp name="INVERT">false</boolProp>
+              <boolProp name="ISREGEX">false</boolProp>
+            </JSONPathAssertion>
+            <hashTree/>
+          </hashTree>
+          <LoopController guiclass="LoopControlPanel" testclass="LoopController" testname="loop_controller" enabled="true">
+            <boolProp name="LoopController.continue_forever">true</boolProp>
+            <stringProp name="LoopController.loops">10</stringProp>
+          </LoopController>
+          <hashTree>
+            <CSVDataSet guiclass="TestBeanGUI" testclass="CSVDataSet" testname="CSV Data Set Config" enabled="true">
+              <stringProp name="delimiter">,</stringProp>
+              <stringProp name="fileEncoding"></stringProp>
+              <stringProp name="filename">iot_data.txt</stringProp>
+              <boolProp name="ignoreFirstLine">false</boolProp>
+              <boolProp name="quotedData">false</boolProp>
+              <boolProp name="recycle">true</boolProp>
+              <stringProp name="shareMode">shareMode.thread</stringProp>
+              <boolProp name="stopThread">false</boolProp>
+              <stringProp name="variableNames">device_id,temperature,humidity</stringProp>
+            </CSVDataSet>
+            <hashTree/>
+            <net.xmeter.samplers.PubSampler guiclass="net.xmeter.gui.PubSamplerUI" testclass="net.xmeter.samplers.PubSampler" testname="MQTT Pub Sampler" enabled="true">
+              <stringProp name="mqtt.topic_name">devices/${device_id}/messages</stringProp>
+              <stringProp name="mqtt.qos_level">0</stringProp>
+              <boolProp name="mqtt.add_timestamp">false</boolProp>
+              <stringProp name="mqtt.message_type">String</stringProp>
+              <stringProp name="mqtt.message_type_fixed_length">1024</stringProp>
+              <stringProp name="mqtt.message_to_sent">{&quot;temperature&quot;: ${temperature}, &quot;humidity&quot; : ${humidity}}</stringProp>
+            </net.xmeter.samplers.PubSampler>
+            <hashTree/>
+          </hashTree>
+          <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="API_GetRuleStatus" enabled="true">
+            <boolProp name="HTTPSampler.postBodyRaw">true</boolProp>
+            <elementProp name="HTTPsampler.Arguments" elementType="Arguments">
+              <collectionProp name="Arguments.arguments">
+                <elementProp name="" elementType="HTTPArgument">
+                  <boolProp name="HTTPArgument.always_encode">false</boolProp>
+                  <stringProp name="Argument.value"></stringProp>
+                  <stringProp name="Argument.metadata">=</stringProp>
+                </elementProp>
+              </collectionProp>
+            </elementProp>
+            <stringProp name="HTTPSampler.domain">${srv}</stringProp>
+            <stringProp name="HTTPSampler.port">${rest_port}</stringProp>
+            <stringProp name="HTTPSampler.protocol"></stringProp>
+            <stringProp name="HTTPSampler.contentEncoding"></stringProp>
+            <stringProp name="HTTPSampler.path">/rules/rule1/status</stringProp>
+            <stringProp name="HTTPSampler.method">GET</stringProp>
+            <boolProp name="HTTPSampler.follow_redirects">true</boolProp>
+            <boolProp name="HTTPSampler.auto_redirects">false</boolProp>
+            <boolProp name="HTTPSampler.use_keepalive">true</boolProp>
+            <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp>
+            <stringProp name="HTTPSampler.embedded_url_re"></stringProp>
+            <stringProp name="HTTPSampler.connect_timeout"></stringProp>
+            <stringProp name="HTTPSampler.response_timeout"></stringProp>
+          </HTTPSamplerProxy>
+          <hashTree>
+            <JSONPathAssertion guiclass="JSONPathAssertionGui" testclass="JSONPathAssertion" testname="JSON Assertion" enabled="true">
+              <stringProp name="JSON_PATH">$.source_demo_0_records_in_total</stringProp>
+              <stringProp name="EXPECTED_VALUE">10</stringProp>
+              <boolProp name="JSONVALIDATION">true</boolProp>
+              <boolProp name="EXPECT_NULL">false</boolProp>
+              <boolProp name="INVERT">false</boolProp>
+              <boolProp name="ISREGEX">false</boolProp>
+            </JSONPathAssertion>
+            <hashTree/>
+            <JSONPathAssertion guiclass="JSONPathAssertionGui" testclass="JSONPathAssertion" testname="JSON Assertion" enabled="false">
+              <stringProp name="JSON_PATH">$.sink_sink_mqtt_0_records_in_total</stringProp>
+              <stringProp name="EXPECTED_VALUE">6</stringProp>
+              <boolProp name="JSONVALIDATION">true</boolProp>
+              <boolProp name="EXPECT_NULL">false</boolProp>
+              <boolProp name="INVERT">false</boolProp>
+              <boolProp name="ISREGEX">false</boolProp>
+            </JSONPathAssertion>
+            <hashTree/>
+            <ConstantTimer guiclass="ConstantTimerGui" testclass="ConstantTimer" testname="Constant Timer" enabled="true">
+              <stringProp name="ConstantTimer.delay">5000</stringProp>
+            </ConstantTimer>
+            <hashTree/>
+          </hashTree>
+          <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="API_DropRule" enabled="true">
+            <boolProp name="HTTPSampler.postBodyRaw">true</boolProp>
+            <elementProp name="HTTPsampler.Arguments" elementType="Arguments">
+              <collectionProp name="Arguments.arguments">
+                <elementProp name="" elementType="HTTPArgument">
+                  <boolProp name="HTTPArgument.always_encode">false</boolProp>
+                  <stringProp name="Argument.value"></stringProp>
+                  <stringProp name="Argument.metadata">=</stringProp>
+                </elementProp>
+              </collectionProp>
+            </elementProp>
+            <stringProp name="HTTPSampler.domain">${srv}</stringProp>
+            <stringProp name="HTTPSampler.port">${rest_port}</stringProp>
+            <stringProp name="HTTPSampler.protocol"></stringProp>
+            <stringProp name="HTTPSampler.contentEncoding"></stringProp>
+            <stringProp name="HTTPSampler.path">/rules/rule1</stringProp>
+            <stringProp name="HTTPSampler.method">DELETE</stringProp>
+            <boolProp name="HTTPSampler.follow_redirects">true</boolProp>
+            <boolProp name="HTTPSampler.auto_redirects">false</boolProp>
+            <boolProp name="HTTPSampler.use_keepalive">true</boolProp>
+            <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp>
+            <stringProp name="HTTPSampler.embedded_url_re"></stringProp>
+            <stringProp name="HTTPSampler.connect_timeout"></stringProp>
+            <stringProp name="HTTPSampler.response_timeout"></stringProp>
+          </HTTPSamplerProxy>
+          <hashTree>
+            <ResponseAssertion guiclass="AssertionGui" testclass="ResponseAssertion" testname="Response Assertion" enabled="true">
+              <collectionProp name="Asserion.test_strings">
+                <stringProp name="717250485">Rule rule1 is dropped.</stringProp>
+              </collectionProp>
+              <stringProp name="Assertion.custom_message"></stringProp>
+              <stringProp name="Assertion.test_field">Assertion.response_data</stringProp>
+              <boolProp name="Assertion.assume_success">false</boolProp>
+              <intProp name="Assertion.test_type">16</intProp>
+            </ResponseAssertion>
+            <hashTree/>
+          </hashTree>
+          <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="API_Drop_Stream" enabled="true">
+            <boolProp name="HTTPSampler.postBodyRaw">true</boolProp>
+            <elementProp name="HTTPsampler.Arguments" elementType="Arguments">
+              <collectionProp name="Arguments.arguments">
+                <elementProp name="" elementType="HTTPArgument">
+                  <boolProp name="HTTPArgument.always_encode">false</boolProp>
+                  <stringProp name="Argument.value"></stringProp>
+                  <stringProp name="Argument.metadata">=</stringProp>
+                </elementProp>
+              </collectionProp>
+            </elementProp>
+            <stringProp name="HTTPSampler.domain">${srv}</stringProp>
+            <stringProp name="HTTPSampler.port">${rest_port}</stringProp>
+            <stringProp name="HTTPSampler.protocol"></stringProp>
+            <stringProp name="HTTPSampler.contentEncoding"></stringProp>
+            <stringProp name="HTTPSampler.path">/streams/demo</stringProp>
+            <stringProp name="HTTPSampler.method">DELETE</stringProp>
+            <boolProp name="HTTPSampler.follow_redirects">true</boolProp>
+            <boolProp name="HTTPSampler.auto_redirects">false</boolProp>
+            <boolProp name="HTTPSampler.use_keepalive">true</boolProp>
+            <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp>
+            <stringProp name="HTTPSampler.embedded_url_re"></stringProp>
+            <stringProp name="HTTPSampler.connect_timeout"></stringProp>
+            <stringProp name="HTTPSampler.response_timeout"></stringProp>
+          </HTTPSamplerProxy>
+          <hashTree>
+            <ResponseAssertion guiclass="AssertionGui" testclass="ResponseAssertion" testname="Response Assertion" enabled="true">
+              <collectionProp name="Asserion.test_strings">
+                <stringProp name="287881319">Stream demo is dropped.</stringProp>
+              </collectionProp>
+              <stringProp name="Assertion.custom_message"></stringProp>
+              <stringProp name="Assertion.test_field">Assertion.response_data</stringProp>
+              <boolProp name="Assertion.assume_success">false</boolProp>
+              <intProp name="Assertion.test_type">16</intProp>
+            </ResponseAssertion>
+            <hashTree/>
+          </hashTree>
+          <ConstantTimer guiclass="ConstantTimerGui" testclass="ConstantTimer" testname="Constant Timer" enabled="true">
+            <stringProp name="ConstantTimer.delay">500</stringProp>
+          </ConstantTimer>
+          <hashTree/>
+        </hashTree>
+      </hashTree>
+      <ThreadGroup guiclass="ThreadGroupGui" testclass="ThreadGroup" testname="Result" enabled="true">
+        <stringProp name="ThreadGroup.on_sample_error">continue</stringProp>
+        <elementProp name="ThreadGroup.main_controller" elementType="LoopController" guiclass="LoopControlPanel" testclass="LoopController" testname="Loop Controller" enabled="true">
+          <boolProp name="LoopController.continue_forever">false</boolProp>
+          <stringProp name="LoopController.loops">1</stringProp>
+        </elementProp>
+        <stringProp name="ThreadGroup.num_threads">1</stringProp>
+        <stringProp name="ThreadGroup.ramp_time">1</stringProp>
+        <boolProp name="ThreadGroup.scheduler">false</boolProp>
+        <stringProp name="ThreadGroup.duration"></stringProp>
+        <stringProp name="ThreadGroup.delay"></stringProp>
+      </ThreadGroup>
+      <hashTree>
+        <ResultCollector guiclass="ViewResultsFullVisualizer" testclass="ResultCollector" testname="View Results Tree" enabled="true">
+          <boolProp name="ResultCollector.error_logging">false</boolProp>
+          <objProp>
+            <name>saveConfig</name>
+            <value class="SampleSaveConfiguration">
+              <time>true</time>
+              <latency>true</latency>
+              <timestamp>true</timestamp>
+              <success>true</success>
+              <label>true</label>
+              <code>true</code>
+              <message>true</message>
+              <threadName>true</threadName>
+              <dataType>true</dataType>
+              <encoding>false</encoding>
+              <assertions>true</assertions>
+              <subresults>true</subresults>
+              <responseData>false</responseData>
+              <samplerData>false</samplerData>
+              <xml>false</xml>
+              <fieldNames>true</fieldNames>
+              <responseHeaders>false</responseHeaders>
+              <requestHeaders>false</requestHeaders>
+              <responseDataOnError>false</responseDataOnError>
+              <saveAssertionResultsFailureMessage>true</saveAssertionResultsFailureMessage>
+              <assertionsResultsToSave>0</assertionsResultsToSave>
+              <bytes>true</bytes>
+              <sentBytes>true</sentBytes>
+              <threadCounts>true</threadCounts>
+              <idleTime>true</idleTime>
+              <connectTime>true</connectTime>
+            </value>
+          </objProp>
+          <stringProp name="filename"></stringProp>
+        </ResultCollector>
+        <hashTree/>
+        <OnceOnlyController guiclass="OnceOnlyControllerGui" testclass="OnceOnlyController" testname="Once Only Controller" enabled="true"/>
+        <hashTree>
+          <net.xmeter.samplers.ConnectSampler guiclass="net.xmeter.gui.ConnectSamplerUI" testclass="net.xmeter.samplers.ConnectSampler" testname="MQTT Connect" enabled="true">
+            <stringProp name="mqtt.server">${mqtt_srv}</stringProp>
+            <stringProp name="mqtt.port">1883</stringProp>
+            <stringProp name="mqtt.version">3.1</stringProp>
+            <stringProp name="mqtt.conn_timeout">10</stringProp>
+            <boolProp name="mqtt.private_protocol">false</boolProp>
+            <stringProp name="mqtt.listener_timeout">10</stringProp>
+            <stringProp name="mqtt.protocol">TCP</stringProp>
+            <boolProp name="mqtt.dual_ssl_authentication">false</boolProp>
+            <stringProp name="mqtt.keystore_file_path"></stringProp>
+            <stringProp name="mqtt.keystore_password"></stringProp>
+            <stringProp name="mqtt.clientcert_file_path"></stringProp>
+            <stringProp name="mqtt.clientcert_password"></stringProp>
+            <stringProp name="mqtt.user_name"></stringProp>
+            <stringProp name="mqtt.password"></stringProp>
+            <stringProp name="mqtt.client_id_prefix">conn_</stringProp>
+            <boolProp name="mqtt.client_id_suffix">true</boolProp>
+            <stringProp name="mqtt.conn_keep_alive">300</stringProp>
+            <stringProp name="mqtt.conn_attampt_max">0</stringProp>
+            <stringProp name="mqtt.reconn_attampt_max">0</stringProp>
+          </net.xmeter.samplers.ConnectSampler>
+          <hashTree/>
+        </hashTree>
+        <net.xmeter.samplers.SubSampler guiclass="net.xmeter.gui.SubSamplerUI" testclass="net.xmeter.samplers.SubSampler" testname="AnalysisResult" enabled="true">
+          <stringProp name="mqtt.topic_name">devices/result</stringProp>
+          <stringProp name="mqtt.qos_level">0</stringProp>
+          <boolProp name="mqtt.add_timestamp">false</boolProp>
+          <boolProp name="mqtt.debug_response">true</boolProp>
+          <stringProp name="mqtt.sample_condition">specified elapsed time (ms)</stringProp>
+          <stringProp name="mqtt.sample_condition_value">15000</stringProp>
+        </net.xmeter.samplers.SubSampler>
+        <hashTree>
+          <BeanShellAssertion guiclass="BeanShellAssertionGui" testclass="BeanShellAssertion" testname="BeanShell Assertion" enabled="true">
+            <stringProp name="BeanShellAssertion.query">import net.sf.json.JSONArray;
+import net.sf.json.JSONObject;
+
+String response = SampleResult.getResponseDataAsString();
+
+JSONArray jsonArr = JSONArray.fromObject(response);
+
+int oldT = 0;
+String dId = &quot;2&quot;;
+
+for(int j = 0; j &lt; jsonArr.size(); j++) {
+	String deviceId = jsonArr.getJSONObject(j).getString(&quot;device_id&quot;);
+	int temperature = jsonArr.getJSONObject(j).getInt(&quot;temperature&quot;);
+
+	if(!dId.equals(deviceId)){
+		dId = deviceId;
+		oldT = 0;
+	}
+	if(oldT &gt; temperature) {
+		Failure = true;
+		FailureMessage = &quot;The analyis result is not by order.&quot;;
+		break;
+	}
+	oldT = temperature;
+}</stringProp>
+            <stringProp name="BeanShellAssertion.filename"></stringProp>
+            <stringProp name="BeanShellAssertion.parameters"></stringProp>
+            <boolProp name="BeanShellAssertion.resetInterpreter">false</boolProp>
+          </BeanShellAssertion>
+          <hashTree/>
+        </hashTree>
+      </hashTree>
+    </hashTree>
+  </hashTree>
+</jmeterTestPlan>

+ 508 - 0
fvt_scripts/select_all_rule.jmx

@@ -0,0 +1,508 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<jmeterTestPlan version="1.2" properties="4.0" jmeter="4.0 r1823414">
+  <hashTree>
+    <TestPlan guiclass="TestPlanGui" testclass="TestPlan" testname="Test Plan" enabled="true">
+      <stringProp name="TestPlan.comments"></stringProp>
+      <boolProp name="TestPlan.functional_mode">false</boolProp>
+      <boolProp name="TestPlan.tearDown_on_shutdown">true</boolProp>
+      <boolProp name="TestPlan.serialize_threadgroups">false</boolProp>
+      <elementProp name="TestPlan.user_defined_variables" elementType="Arguments" guiclass="ArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true">
+        <collectionProp name="Arguments.arguments"/>
+      </elementProp>
+      <stringProp name="TestPlan.user_define_classpath"></stringProp>
+    </TestPlan>
+    <hashTree>
+      <Arguments guiclass="ArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true">
+        <collectionProp name="Arguments.arguments">
+          <elementProp name="srv" elementType="Argument">
+            <stringProp name="Argument.name">srv</stringProp>
+            <stringProp name="Argument.value">127.0.0.1</stringProp>
+            <stringProp name="Argument.metadata">=</stringProp>
+          </elementProp>
+          <elementProp name="rest_port" elementType="Argument">
+            <stringProp name="Argument.name">rest_port</stringProp>
+            <stringProp name="Argument.value">9081</stringProp>
+            <stringProp name="Argument.metadata">=</stringProp>
+          </elementProp>
+          <elementProp name="mqtt_srv" elementType="Argument">
+            <stringProp name="Argument.name">mqtt_srv</stringProp>
+            <stringProp name="Argument.value">127.0.0.1</stringProp>
+            <stringProp name="Argument.metadata">=</stringProp>
+          </elementProp>
+        </collectionProp>
+      </Arguments>
+      <hashTree/>
+      <ThreadGroup guiclass="ThreadGroupGui" testclass="ThreadGroup" testname="Rules" enabled="true">
+        <stringProp name="ThreadGroup.on_sample_error">continue</stringProp>
+        <elementProp name="ThreadGroup.main_controller" elementType="LoopController" guiclass="LoopControlPanel" testclass="LoopController" testname="Loop Controller" enabled="true">
+          <boolProp name="LoopController.continue_forever">false</boolProp>
+          <stringProp name="LoopController.loops">1</stringProp>
+        </elementProp>
+        <stringProp name="ThreadGroup.num_threads">1</stringProp>
+        <stringProp name="ThreadGroup.ramp_time">1</stringProp>
+        <boolProp name="ThreadGroup.scheduler">false</boolProp>
+        <stringProp name="ThreadGroup.duration"></stringProp>
+        <stringProp name="ThreadGroup.delay"></stringProp>
+      </ThreadGroup>
+      <hashTree>
+        <ResultCollector guiclass="ViewResultsFullVisualizer" testclass="ResultCollector" testname="View Results Tree" enabled="true">
+          <boolProp name="ResultCollector.error_logging">false</boolProp>
+          <objProp>
+            <name>saveConfig</name>
+            <value class="SampleSaveConfiguration">
+              <time>true</time>
+              <latency>true</latency>
+              <timestamp>true</timestamp>
+              <success>true</success>
+              <label>true</label>
+              <code>true</code>
+              <message>true</message>
+              <threadName>true</threadName>
+              <dataType>true</dataType>
+              <encoding>false</encoding>
+              <assertions>true</assertions>
+              <subresults>true</subresults>
+              <responseData>false</responseData>
+              <samplerData>false</samplerData>
+              <xml>false</xml>
+              <fieldNames>true</fieldNames>
+              <responseHeaders>false</responseHeaders>
+              <requestHeaders>false</requestHeaders>
+              <responseDataOnError>false</responseDataOnError>
+              <saveAssertionResultsFailureMessage>true</saveAssertionResultsFailureMessage>
+              <assertionsResultsToSave>0</assertionsResultsToSave>
+              <bytes>true</bytes>
+              <sentBytes>true</sentBytes>
+              <threadCounts>true</threadCounts>
+              <idleTime>true</idleTime>
+              <connectTime>true</connectTime>
+            </value>
+          </objProp>
+          <stringProp name="filename"></stringProp>
+        </ResultCollector>
+        <hashTree/>
+        <TransactionController guiclass="TransactionControllerGui" testclass="TransactionController" testname="API" enabled="true">
+          <boolProp name="TransactionController.includeTimers">false</boolProp>
+          <boolProp name="TransactionController.parent">false</boolProp>
+        </TransactionController>
+        <hashTree>
+          <net.xmeter.samplers.ConnectSampler guiclass="net.xmeter.gui.ConnectSamplerUI" testclass="net.xmeter.samplers.ConnectSampler" testname="MQTT Connect" enabled="true">
+            <stringProp name="mqtt.server">${mqtt_srv}</stringProp>
+            <stringProp name="mqtt.port">1883</stringProp>
+            <stringProp name="mqtt.version">3.1</stringProp>
+            <stringProp name="mqtt.conn_timeout">10</stringProp>
+            <boolProp name="mqtt.private_protocol">false</boolProp>
+            <stringProp name="mqtt.listener_timeout">10</stringProp>
+            <stringProp name="mqtt.protocol">TCP</stringProp>
+            <boolProp name="mqtt.dual_ssl_authentication">false</boolProp>
+            <stringProp name="mqtt.keystore_file_path"></stringProp>
+            <stringProp name="mqtt.keystore_password"></stringProp>
+            <stringProp name="mqtt.clientcert_file_path"></stringProp>
+            <stringProp name="mqtt.clientcert_password"></stringProp>
+            <stringProp name="mqtt.user_name"></stringProp>
+            <stringProp name="mqtt.password"></stringProp>
+            <stringProp name="mqtt.client_id_prefix">conn_</stringProp>
+            <boolProp name="mqtt.client_id_suffix">true</boolProp>
+            <stringProp name="mqtt.conn_keep_alive">300</stringProp>
+            <stringProp name="mqtt.conn_attampt_max">0</stringProp>
+            <stringProp name="mqtt.reconn_attampt_max">0</stringProp>
+          </net.xmeter.samplers.ConnectSampler>
+          <hashTree/>
+          <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="API_CreateStream" enabled="true">
+            <boolProp name="HTTPSampler.postBodyRaw">true</boolProp>
+            <elementProp name="HTTPsampler.Arguments" elementType="Arguments">
+              <collectionProp name="Arguments.arguments">
+                <elementProp name="" elementType="HTTPArgument">
+                  <boolProp name="HTTPArgument.always_encode">false</boolProp>
+                  <stringProp name="Argument.value">{&#xd;
+&quot;sql&quot; : &quot;create stream demo (temperature float, humidity bigint) WITH (FORMAT=\&quot;JSON\&quot;, DATASOURCE=\&quot;devices/+/messages\&quot; )&quot;&#xd;
+}</stringProp>
+                  <stringProp name="Argument.metadata">=</stringProp>
+                </elementProp>
+              </collectionProp>
+            </elementProp>
+            <stringProp name="HTTPSampler.domain">${srv}</stringProp>
+            <stringProp name="HTTPSampler.port">${rest_port}</stringProp>
+            <stringProp name="HTTPSampler.protocol"></stringProp>
+            <stringProp name="HTTPSampler.contentEncoding"></stringProp>
+            <stringProp name="HTTPSampler.path">/streams</stringProp>
+            <stringProp name="HTTPSampler.method">POST</stringProp>
+            <boolProp name="HTTPSampler.follow_redirects">true</boolProp>
+            <boolProp name="HTTPSampler.auto_redirects">false</boolProp>
+            <boolProp name="HTTPSampler.use_keepalive">true</boolProp>
+            <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp>
+            <stringProp name="HTTPSampler.embedded_url_re"></stringProp>
+            <stringProp name="HTTPSampler.connect_timeout"></stringProp>
+            <stringProp name="HTTPSampler.response_timeout"></stringProp>
+          </HTTPSamplerProxy>
+          <hashTree>
+            <ResponseAssertion guiclass="AssertionGui" testclass="ResponseAssertion" testname="Response Assertion" enabled="true">
+              <collectionProp name="Asserion.test_strings">
+                <stringProp name="-1754954177">Stream demo is created.</stringProp>
+              </collectionProp>
+              <stringProp name="Assertion.custom_message"></stringProp>
+              <stringProp name="Assertion.test_field">Assertion.response_data</stringProp>
+              <boolProp name="Assertion.assume_success">true</boolProp>
+              <intProp name="Assertion.test_type">16</intProp>
+            </ResponseAssertion>
+            <hashTree/>
+          </hashTree>
+          <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="API_CreateRule" enabled="true">
+            <boolProp name="HTTPSampler.postBodyRaw">true</boolProp>
+            <elementProp name="HTTPsampler.Arguments" elementType="Arguments">
+              <collectionProp name="Arguments.arguments">
+                <elementProp name="" elementType="HTTPArgument">
+                  <boolProp name="HTTPArgument.always_encode">false</boolProp>
+                  <stringProp name="Argument.value">{&#xd;
+  &quot;id&quot;: &quot;rule1&quot;,&#xd;
+  &quot;sql&quot;: &quot;SELECT * FROM demo&quot;,&#xd;
+  &quot;actions&quot;: [&#xd;
+    {&#xd;
+      &quot;log&quot;: {}&#xd;
+    },&#xd;
+    {&#xd;
+      &quot;mqtt&quot;: {&#xd;
+        &quot;server&quot;: &quot;tcp://${mqtt_srv}:1883&quot;,&#xd;
+        &quot;topic&quot;: &quot;devices/result&quot;,&#xd;
+        &quot;qos&quot;: 1,&#xd;
+        &quot;clientId&quot;: &quot;demo_001&quot;&#xd;
+      }&#xd;
+    }&#xd;
+  ]&#xd;
+}</stringProp>
+                  <stringProp name="Argument.metadata">=</stringProp>
+                </elementProp>
+              </collectionProp>
+            </elementProp>
+            <stringProp name="HTTPSampler.domain">${srv}</stringProp>
+            <stringProp name="HTTPSampler.port">${rest_port}</stringProp>
+            <stringProp name="HTTPSampler.protocol"></stringProp>
+            <stringProp name="HTTPSampler.contentEncoding"></stringProp>
+            <stringProp name="HTTPSampler.path">/rules</stringProp>
+            <stringProp name="HTTPSampler.method">POST</stringProp>
+            <boolProp name="HTTPSampler.follow_redirects">true</boolProp>
+            <boolProp name="HTTPSampler.auto_redirects">false</boolProp>
+            <boolProp name="HTTPSampler.use_keepalive">true</boolProp>
+            <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp>
+            <stringProp name="HTTPSampler.embedded_url_re"></stringProp>
+            <stringProp name="HTTPSampler.connect_timeout"></stringProp>
+            <stringProp name="HTTPSampler.response_timeout"></stringProp>
+          </HTTPSamplerProxy>
+          <hashTree>
+            <ResponseAssertion guiclass="AssertionGui" testclass="ResponseAssertion" testname="Response Assertion" enabled="true">
+              <collectionProp name="Asserion.test_strings">
+                <stringProp name="-2022196798">Rule rule1 was created</stringProp>
+              </collectionProp>
+              <stringProp name="Assertion.custom_message"></stringProp>
+              <stringProp name="Assertion.test_field">Assertion.response_data</stringProp>
+              <boolProp name="Assertion.assume_success">true</boolProp>
+              <intProp name="Assertion.test_type">16</intProp>
+            </ResponseAssertion>
+            <hashTree/>
+          </hashTree>
+          <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="API_GetRuleStatus" enabled="true">
+            <boolProp name="HTTPSampler.postBodyRaw">true</boolProp>
+            <elementProp name="HTTPsampler.Arguments" elementType="Arguments">
+              <collectionProp name="Arguments.arguments">
+                <elementProp name="" elementType="HTTPArgument">
+                  <boolProp name="HTTPArgument.always_encode">false</boolProp>
+                  <stringProp name="Argument.value"></stringProp>
+                  <stringProp name="Argument.metadata">=</stringProp>
+                </elementProp>
+              </collectionProp>
+            </elementProp>
+            <stringProp name="HTTPSampler.domain">${srv}</stringProp>
+            <stringProp name="HTTPSampler.port">${rest_port}</stringProp>
+            <stringProp name="HTTPSampler.protocol"></stringProp>
+            <stringProp name="HTTPSampler.contentEncoding"></stringProp>
+            <stringProp name="HTTPSampler.path">/rules/rule1/status</stringProp>
+            <stringProp name="HTTPSampler.method">GET</stringProp>
+            <boolProp name="HTTPSampler.follow_redirects">true</boolProp>
+            <boolProp name="HTTPSampler.auto_redirects">false</boolProp>
+            <boolProp name="HTTPSampler.use_keepalive">true</boolProp>
+            <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp>
+            <stringProp name="HTTPSampler.embedded_url_re"></stringProp>
+            <stringProp name="HTTPSampler.connect_timeout"></stringProp>
+            <stringProp name="HTTPSampler.response_timeout"></stringProp>
+          </HTTPSamplerProxy>
+          <hashTree>
+            <JSONPathAssertion guiclass="JSONPathAssertionGui" testclass="JSONPathAssertion" testname="JSON Assertion" enabled="true">
+              <stringProp name="JSON_PATH">$.source_demo_0_records_in_total</stringProp>
+              <stringProp name="EXPECTED_VALUE">0</stringProp>
+              <boolProp name="JSONVALIDATION">true</boolProp>
+              <boolProp name="EXPECT_NULL">false</boolProp>
+              <boolProp name="INVERT">false</boolProp>
+              <boolProp name="ISREGEX">false</boolProp>
+            </JSONPathAssertion>
+            <hashTree/>
+          </hashTree>
+          <LoopController guiclass="LoopControlPanel" testclass="LoopController" testname="loop_controller" enabled="true">
+            <boolProp name="LoopController.continue_forever">true</boolProp>
+            <stringProp name="LoopController.loops">10</stringProp>
+          </LoopController>
+          <hashTree>
+            <CSVDataSet guiclass="TestBeanGUI" testclass="CSVDataSet" testname="CSV Data Set Config" enabled="true">
+              <stringProp name="delimiter">,</stringProp>
+              <stringProp name="fileEncoding"></stringProp>
+              <stringProp name="filename">iot_data.txt</stringProp>
+              <boolProp name="ignoreFirstLine">false</boolProp>
+              <boolProp name="quotedData">false</boolProp>
+              <boolProp name="recycle">true</boolProp>
+              <stringProp name="shareMode">shareMode.thread</stringProp>
+              <boolProp name="stopThread">false</boolProp>
+              <stringProp name="variableNames">device_id,temperature,humidity</stringProp>
+            </CSVDataSet>
+            <hashTree/>
+            <net.xmeter.samplers.PubSampler guiclass="net.xmeter.gui.PubSamplerUI" testclass="net.xmeter.samplers.PubSampler" testname="MQTT Pub Sampler" enabled="true">
+              <stringProp name="mqtt.topic_name">devices/${device_id}/messages</stringProp>
+              <stringProp name="mqtt.qos_level">0</stringProp>
+              <boolProp name="mqtt.add_timestamp">false</boolProp>
+              <stringProp name="mqtt.message_type">String</stringProp>
+              <stringProp name="mqtt.message_type_fixed_length">1024</stringProp>
+              <stringProp name="mqtt.message_to_sent">{&quot;temperature&quot;: ${temperature}, &quot;humidity&quot; : ${humidity}}</stringProp>
+            </net.xmeter.samplers.PubSampler>
+            <hashTree/>
+          </hashTree>
+          <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="API_GetRuleStatus" enabled="true">
+            <boolProp name="HTTPSampler.postBodyRaw">true</boolProp>
+            <elementProp name="HTTPsampler.Arguments" elementType="Arguments">
+              <collectionProp name="Arguments.arguments">
+                <elementProp name="" elementType="HTTPArgument">
+                  <boolProp name="HTTPArgument.always_encode">false</boolProp>
+                  <stringProp name="Argument.value"></stringProp>
+                  <stringProp name="Argument.metadata">=</stringProp>
+                </elementProp>
+              </collectionProp>
+            </elementProp>
+            <stringProp name="HTTPSampler.domain">${srv}</stringProp>
+            <stringProp name="HTTPSampler.port">${rest_port}</stringProp>
+            <stringProp name="HTTPSampler.protocol"></stringProp>
+            <stringProp name="HTTPSampler.contentEncoding"></stringProp>
+            <stringProp name="HTTPSampler.path">/rules/rule1/status</stringProp>
+            <stringProp name="HTTPSampler.method">GET</stringProp>
+            <boolProp name="HTTPSampler.follow_redirects">true</boolProp>
+            <boolProp name="HTTPSampler.auto_redirects">false</boolProp>
+            <boolProp name="HTTPSampler.use_keepalive">true</boolProp>
+            <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp>
+            <stringProp name="HTTPSampler.embedded_url_re"></stringProp>
+            <stringProp name="HTTPSampler.connect_timeout"></stringProp>
+            <stringProp name="HTTPSampler.response_timeout"></stringProp>
+          </HTTPSamplerProxy>
+          <hashTree>
+            <JSONPathAssertion guiclass="JSONPathAssertionGui" testclass="JSONPathAssertion" testname="JSON Assertion" enabled="true">
+              <stringProp name="JSON_PATH">$.source_demo_0_records_in_total</stringProp>
+              <stringProp name="EXPECTED_VALUE">10</stringProp>
+              <boolProp name="JSONVALIDATION">true</boolProp>
+              <boolProp name="EXPECT_NULL">false</boolProp>
+              <boolProp name="INVERT">false</boolProp>
+              <boolProp name="ISREGEX">false</boolProp>
+            </JSONPathAssertion>
+            <hashTree/>
+            <JSONPathAssertion guiclass="JSONPathAssertionGui" testclass="JSONPathAssertion" testname="JSON Assertion" enabled="true">
+              <stringProp name="JSON_PATH">$.sink_sink_mqtt_0_records_in_total</stringProp>
+              <stringProp name="EXPECTED_VALUE">10</stringProp>
+              <boolProp name="JSONVALIDATION">true</boolProp>
+              <boolProp name="EXPECT_NULL">false</boolProp>
+              <boolProp name="INVERT">false</boolProp>
+              <boolProp name="ISREGEX">false</boolProp>
+            </JSONPathAssertion>
+            <hashTree/>
+          </hashTree>
+          <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="API_DropRule" enabled="true">
+            <boolProp name="HTTPSampler.postBodyRaw">true</boolProp>
+            <elementProp name="HTTPsampler.Arguments" elementType="Arguments">
+              <collectionProp name="Arguments.arguments">
+                <elementProp name="" elementType="HTTPArgument">
+                  <boolProp name="HTTPArgument.always_encode">false</boolProp>
+                  <stringProp name="Argument.value"></stringProp>
+                  <stringProp name="Argument.metadata">=</stringProp>
+                </elementProp>
+              </collectionProp>
+            </elementProp>
+            <stringProp name="HTTPSampler.domain">${srv}</stringProp>
+            <stringProp name="HTTPSampler.port">${rest_port}</stringProp>
+            <stringProp name="HTTPSampler.protocol"></stringProp>
+            <stringProp name="HTTPSampler.contentEncoding"></stringProp>
+            <stringProp name="HTTPSampler.path">/rules/rule1</stringProp>
+            <stringProp name="HTTPSampler.method">DELETE</stringProp>
+            <boolProp name="HTTPSampler.follow_redirects">true</boolProp>
+            <boolProp name="HTTPSampler.auto_redirects">false</boolProp>
+            <boolProp name="HTTPSampler.use_keepalive">true</boolProp>
+            <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp>
+            <stringProp name="HTTPSampler.embedded_url_re"></stringProp>
+            <stringProp name="HTTPSampler.connect_timeout"></stringProp>
+            <stringProp name="HTTPSampler.response_timeout"></stringProp>
+          </HTTPSamplerProxy>
+          <hashTree>
+            <ResponseAssertion guiclass="AssertionGui" testclass="ResponseAssertion" testname="Response Assertion" enabled="true">
+              <collectionProp name="Asserion.test_strings">
+                <stringProp name="717250485">Rule rule1 is dropped.</stringProp>
+              </collectionProp>
+              <stringProp name="Assertion.custom_message"></stringProp>
+              <stringProp name="Assertion.test_field">Assertion.response_data</stringProp>
+              <boolProp name="Assertion.assume_success">false</boolProp>
+              <intProp name="Assertion.test_type">16</intProp>
+            </ResponseAssertion>
+            <hashTree/>
+          </hashTree>
+          <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="API_Drop_Stream" enabled="true">
+            <boolProp name="HTTPSampler.postBodyRaw">true</boolProp>
+            <elementProp name="HTTPsampler.Arguments" elementType="Arguments">
+              <collectionProp name="Arguments.arguments">
+                <elementProp name="" elementType="HTTPArgument">
+                  <boolProp name="HTTPArgument.always_encode">false</boolProp>
+                  <stringProp name="Argument.value"></stringProp>
+                  <stringProp name="Argument.metadata">=</stringProp>
+                </elementProp>
+              </collectionProp>
+            </elementProp>
+            <stringProp name="HTTPSampler.domain">${srv}</stringProp>
+            <stringProp name="HTTPSampler.port">${rest_port}</stringProp>
+            <stringProp name="HTTPSampler.protocol"></stringProp>
+            <stringProp name="HTTPSampler.contentEncoding"></stringProp>
+            <stringProp name="HTTPSampler.path">/streams/demo</stringProp>
+            <stringProp name="HTTPSampler.method">DELETE</stringProp>
+            <boolProp name="HTTPSampler.follow_redirects">true</boolProp>
+            <boolProp name="HTTPSampler.auto_redirects">false</boolProp>
+            <boolProp name="HTTPSampler.use_keepalive">true</boolProp>
+            <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp>
+            <stringProp name="HTTPSampler.embedded_url_re"></stringProp>
+            <stringProp name="HTTPSampler.connect_timeout"></stringProp>
+            <stringProp name="HTTPSampler.response_timeout"></stringProp>
+          </HTTPSamplerProxy>
+          <hashTree>
+            <ResponseAssertion guiclass="AssertionGui" testclass="ResponseAssertion" testname="Response Assertion" enabled="true">
+              <collectionProp name="Asserion.test_strings">
+                <stringProp name="287881319">Stream demo is dropped.</stringProp>
+              </collectionProp>
+              <stringProp name="Assertion.custom_message"></stringProp>
+              <stringProp name="Assertion.test_field">Assertion.response_data</stringProp>
+              <boolProp name="Assertion.assume_success">false</boolProp>
+              <intProp name="Assertion.test_type">16</intProp>
+            </ResponseAssertion>
+            <hashTree/>
+          </hashTree>
+          <ConstantTimer guiclass="ConstantTimerGui" testclass="ConstantTimer" testname="Constant Timer" enabled="true">
+            <stringProp name="ConstantTimer.delay">500</stringProp>
+          </ConstantTimer>
+          <hashTree/>
+        </hashTree>
+      </hashTree>
+      <ThreadGroup guiclass="ThreadGroupGui" testclass="ThreadGroup" testname="Result" enabled="true">
+        <stringProp name="ThreadGroup.on_sample_error">continue</stringProp>
+        <elementProp name="ThreadGroup.main_controller" elementType="LoopController" guiclass="LoopControlPanel" testclass="LoopController" testname="Loop Controller" enabled="true">
+          <boolProp name="LoopController.continue_forever">false</boolProp>
+          <stringProp name="LoopController.loops">10</stringProp>
+        </elementProp>
+        <stringProp name="ThreadGroup.num_threads">1</stringProp>
+        <stringProp name="ThreadGroup.ramp_time">1</stringProp>
+        <boolProp name="ThreadGroup.scheduler">false</boolProp>
+        <stringProp name="ThreadGroup.duration"></stringProp>
+        <stringProp name="ThreadGroup.delay"></stringProp>
+      </ThreadGroup>
+      <hashTree>
+        <ResultCollector guiclass="ViewResultsFullVisualizer" testclass="ResultCollector" testname="View Results Tree" enabled="true">
+          <boolProp name="ResultCollector.error_logging">false</boolProp>
+          <objProp>
+            <name>saveConfig</name>
+            <value class="SampleSaveConfiguration">
+              <time>true</time>
+              <latency>true</latency>
+              <timestamp>true</timestamp>
+              <success>true</success>
+              <label>true</label>
+              <code>true</code>
+              <message>true</message>
+              <threadName>true</threadName>
+              <dataType>true</dataType>
+              <encoding>false</encoding>
+              <assertions>true</assertions>
+              <subresults>true</subresults>
+              <responseData>false</responseData>
+              <samplerData>false</samplerData>
+              <xml>false</xml>
+              <fieldNames>true</fieldNames>
+              <responseHeaders>false</responseHeaders>
+              <requestHeaders>false</requestHeaders>
+              <responseDataOnError>false</responseDataOnError>
+              <saveAssertionResultsFailureMessage>true</saveAssertionResultsFailureMessage>
+              <assertionsResultsToSave>0</assertionsResultsToSave>
+              <bytes>true</bytes>
+              <sentBytes>true</sentBytes>
+              <threadCounts>true</threadCounts>
+              <idleTime>true</idleTime>
+              <connectTime>true</connectTime>
+            </value>
+          </objProp>
+          <stringProp name="filename"></stringProp>
+        </ResultCollector>
+        <hashTree/>
+        <CSVDataSet guiclass="TestBeanGUI" testclass="CSVDataSet" testname="CSV Data Set Config" enabled="true">
+          <stringProp name="delimiter">,</stringProp>
+          <stringProp name="fileEncoding"></stringProp>
+          <stringProp name="filename">iot_data.txt</stringProp>
+          <boolProp name="ignoreFirstLine">false</boolProp>
+          <boolProp name="quotedData">false</boolProp>
+          <boolProp name="recycle">true</boolProp>
+          <stringProp name="shareMode">shareMode.group</stringProp>
+          <boolProp name="stopThread">false</boolProp>
+          <stringProp name="variableNames">device_id,temperature,humidity</stringProp>
+        </CSVDataSet>
+        <hashTree/>
+        <OnceOnlyController guiclass="OnceOnlyControllerGui" testclass="OnceOnlyController" testname="Once Only Controller" enabled="true"/>
+        <hashTree>
+          <net.xmeter.samplers.ConnectSampler guiclass="net.xmeter.gui.ConnectSamplerUI" testclass="net.xmeter.samplers.ConnectSampler" testname="MQTT Connect" enabled="true">
+            <stringProp name="mqtt.server">${mqtt_srv}</stringProp>
+            <stringProp name="mqtt.port">1883</stringProp>
+            <stringProp name="mqtt.version">3.1</stringProp>
+            <stringProp name="mqtt.conn_timeout">10</stringProp>
+            <boolProp name="mqtt.private_protocol">false</boolProp>
+            <stringProp name="mqtt.listener_timeout">10</stringProp>
+            <stringProp name="mqtt.protocol">TCP</stringProp>
+            <boolProp name="mqtt.dual_ssl_authentication">false</boolProp>
+            <stringProp name="mqtt.keystore_file_path"></stringProp>
+            <stringProp name="mqtt.keystore_password"></stringProp>
+            <stringProp name="mqtt.clientcert_file_path"></stringProp>
+            <stringProp name="mqtt.clientcert_password"></stringProp>
+            <stringProp name="mqtt.user_name"></stringProp>
+            <stringProp name="mqtt.password"></stringProp>
+            <stringProp name="mqtt.client_id_prefix">conn_</stringProp>
+            <boolProp name="mqtt.client_id_suffix">true</boolProp>
+            <stringProp name="mqtt.conn_keep_alive">300</stringProp>
+            <stringProp name="mqtt.conn_attampt_max">0</stringProp>
+            <stringProp name="mqtt.reconn_attampt_max">0</stringProp>
+          </net.xmeter.samplers.ConnectSampler>
+          <hashTree/>
+        </hashTree>
+        <net.xmeter.samplers.SubSampler guiclass="net.xmeter.gui.SubSamplerUI" testclass="net.xmeter.samplers.SubSampler" testname="AnalysisResult" enabled="true">
+          <stringProp name="mqtt.topic_name">devices/result</stringProp>
+          <stringProp name="mqtt.qos_level">0</stringProp>
+          <boolProp name="mqtt.add_timestamp">false</boolProp>
+          <boolProp name="mqtt.debug_response">true</boolProp>
+          <stringProp name="mqtt.sample_condition">number of received messages</stringProp>
+          <stringProp name="mqtt.sample_condition_value">1</stringProp>
+        </net.xmeter.samplers.SubSampler>
+        <hashTree>
+          <JSONPathAssertion guiclass="JSONPathAssertionGui" testclass="JSONPathAssertion" testname="temperature Assertion" enabled="true">
+            <stringProp name="JSON_PATH">$[0].temperature</stringProp>
+            <stringProp name="EXPECTED_VALUE">${temperature}</stringProp>
+            <boolProp name="JSONVALIDATION">true</boolProp>
+            <boolProp name="EXPECT_NULL">false</boolProp>
+            <boolProp name="INVERT">false</boolProp>
+            <boolProp name="ISREGEX">false</boolProp>
+          </JSONPathAssertion>
+          <hashTree/>
+          <JSONPathAssertion guiclass="JSONPathAssertionGui" testclass="JSONPathAssertion" testname="humidity Assertion" enabled="true">
+            <stringProp name="JSON_PATH">$[0].humidity</stringProp>
+            <stringProp name="EXPECTED_VALUE">${humidity}</stringProp>
+            <boolProp name="JSONVALIDATION">true</boolProp>
+            <boolProp name="EXPECT_NULL">false</boolProp>
+            <boolProp name="INVERT">false</boolProp>
+            <boolProp name="ISREGEX">false</boolProp>
+          </JSONPathAssertion>
+          <hashTree/>
+        </hashTree>
+      </hashTree>
+    </hashTree>
+  </hashTree>
+</jmeterTestPlan>

+ 6 - 0
fvt_scripts/select_condition_iot_data.txt

@@ -0,0 +1,6 @@
+2,31,40
+1,35,50
+1,80,90
+2,45,20
+1,65,35
+2,55,32

+ 509 - 0
fvt_scripts/select_condition_rule.jmx

@@ -0,0 +1,509 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<jmeterTestPlan version="1.2" properties="4.0" jmeter="4.0 r1823414">
+  <hashTree>
+    <TestPlan guiclass="TestPlanGui" testclass="TestPlan" testname="Test Plan" enabled="true">
+      <stringProp name="TestPlan.comments"></stringProp>
+      <boolProp name="TestPlan.functional_mode">false</boolProp>
+      <boolProp name="TestPlan.tearDown_on_shutdown">true</boolProp>
+      <boolProp name="TestPlan.serialize_threadgroups">false</boolProp>
+      <elementProp name="TestPlan.user_defined_variables" elementType="Arguments" guiclass="ArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true">
+        <collectionProp name="Arguments.arguments"/>
+      </elementProp>
+      <stringProp name="TestPlan.user_define_classpath"></stringProp>
+    </TestPlan>
+    <hashTree>
+      <Arguments guiclass="ArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true">
+        <collectionProp name="Arguments.arguments">
+          <elementProp name="srv" elementType="Argument">
+            <stringProp name="Argument.name">srv</stringProp>
+            <stringProp name="Argument.value">127.0.0.1</stringProp>
+            <stringProp name="Argument.metadata">=</stringProp>
+          </elementProp>
+          <elementProp name="rest_port" elementType="Argument">
+            <stringProp name="Argument.name">rest_port</stringProp>
+            <stringProp name="Argument.value">9081</stringProp>
+            <stringProp name="Argument.metadata">=</stringProp>
+          </elementProp>
+          <elementProp name="mqtt_srv" elementType="Argument">
+            <stringProp name="Argument.name">mqtt_srv</stringProp>
+            <stringProp name="Argument.value">127.0.0.1</stringProp>
+            <stringProp name="Argument.metadata">=</stringProp>
+          </elementProp>
+        </collectionProp>
+      </Arguments>
+      <hashTree/>
+      <ThreadGroup guiclass="ThreadGroupGui" testclass="ThreadGroup" testname="Rules" enabled="true">
+        <stringProp name="ThreadGroup.on_sample_error">continue</stringProp>
+        <elementProp name="ThreadGroup.main_controller" elementType="LoopController" guiclass="LoopControlPanel" testclass="LoopController" testname="Loop Controller" enabled="true">
+          <boolProp name="LoopController.continue_forever">false</boolProp>
+          <stringProp name="LoopController.loops">1</stringProp>
+        </elementProp>
+        <stringProp name="ThreadGroup.num_threads">1</stringProp>
+        <stringProp name="ThreadGroup.ramp_time">1</stringProp>
+        <boolProp name="ThreadGroup.scheduler">false</boolProp>
+        <stringProp name="ThreadGroup.duration"></stringProp>
+        <stringProp name="ThreadGroup.delay"></stringProp>
+      </ThreadGroup>
+      <hashTree>
+        <ResultCollector guiclass="ViewResultsFullVisualizer" testclass="ResultCollector" testname="View Results Tree" enabled="true">
+          <boolProp name="ResultCollector.error_logging">false</boolProp>
+          <objProp>
+            <name>saveConfig</name>
+            <value class="SampleSaveConfiguration">
+              <time>true</time>
+              <latency>true</latency>
+              <timestamp>true</timestamp>
+              <success>true</success>
+              <label>true</label>
+              <code>true</code>
+              <message>true</message>
+              <threadName>true</threadName>
+              <dataType>true</dataType>
+              <encoding>false</encoding>
+              <assertions>true</assertions>
+              <subresults>true</subresults>
+              <responseData>false</responseData>
+              <samplerData>false</samplerData>
+              <xml>false</xml>
+              <fieldNames>true</fieldNames>
+              <responseHeaders>false</responseHeaders>
+              <requestHeaders>false</requestHeaders>
+              <responseDataOnError>false</responseDataOnError>
+              <saveAssertionResultsFailureMessage>true</saveAssertionResultsFailureMessage>
+              <assertionsResultsToSave>0</assertionsResultsToSave>
+              <bytes>true</bytes>
+              <sentBytes>true</sentBytes>
+              <threadCounts>true</threadCounts>
+              <idleTime>true</idleTime>
+              <connectTime>true</connectTime>
+            </value>
+          </objProp>
+          <stringProp name="filename"></stringProp>
+        </ResultCollector>
+        <hashTree/>
+        <TransactionController guiclass="TransactionControllerGui" testclass="TransactionController" testname="API" enabled="true">
+          <boolProp name="TransactionController.includeTimers">false</boolProp>
+          <boolProp name="TransactionController.parent">false</boolProp>
+        </TransactionController>
+        <hashTree>
+          <net.xmeter.samplers.ConnectSampler guiclass="net.xmeter.gui.ConnectSamplerUI" testclass="net.xmeter.samplers.ConnectSampler" testname="MQTT Connect" enabled="true">
+            <stringProp name="mqtt.server">${mqtt_srv}</stringProp>
+            <stringProp name="mqtt.port">1883</stringProp>
+            <stringProp name="mqtt.version">3.1</stringProp>
+            <stringProp name="mqtt.conn_timeout">10</stringProp>
+            <boolProp name="mqtt.private_protocol">false</boolProp>
+            <stringProp name="mqtt.listener_timeout">10</stringProp>
+            <stringProp name="mqtt.protocol">TCP</stringProp>
+            <boolProp name="mqtt.dual_ssl_authentication">false</boolProp>
+            <stringProp name="mqtt.keystore_file_path"></stringProp>
+            <stringProp name="mqtt.keystore_password"></stringProp>
+            <stringProp name="mqtt.clientcert_file_path"></stringProp>
+            <stringProp name="mqtt.clientcert_password"></stringProp>
+            <stringProp name="mqtt.user_name"></stringProp>
+            <stringProp name="mqtt.password"></stringProp>
+            <stringProp name="mqtt.client_id_prefix">conn_</stringProp>
+            <boolProp name="mqtt.client_id_suffix">true</boolProp>
+            <stringProp name="mqtt.conn_keep_alive">300</stringProp>
+            <stringProp name="mqtt.conn_attampt_max">0</stringProp>
+            <stringProp name="mqtt.reconn_attampt_max">0</stringProp>
+          </net.xmeter.samplers.ConnectSampler>
+          <hashTree/>
+          <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="API_CreateStream" enabled="true">
+            <boolProp name="HTTPSampler.postBodyRaw">true</boolProp>
+            <elementProp name="HTTPsampler.Arguments" elementType="Arguments">
+              <collectionProp name="Arguments.arguments">
+                <elementProp name="" elementType="HTTPArgument">
+                  <boolProp name="HTTPArgument.always_encode">false</boolProp>
+                  <stringProp name="Argument.value">{&#xd;
+&quot;sql&quot; : &quot;create stream demo (temperature float, humidity bigint) WITH (FORMAT=\&quot;JSON\&quot;, DATASOURCE=\&quot;devices/+/messages\&quot; )&quot;&#xd;
+}</stringProp>
+                  <stringProp name="Argument.metadata">=</stringProp>
+                </elementProp>
+              </collectionProp>
+            </elementProp>
+            <stringProp name="HTTPSampler.domain">${srv}</stringProp>
+            <stringProp name="HTTPSampler.port">${rest_port}</stringProp>
+            <stringProp name="HTTPSampler.protocol"></stringProp>
+            <stringProp name="HTTPSampler.contentEncoding"></stringProp>
+            <stringProp name="HTTPSampler.path">/streams</stringProp>
+            <stringProp name="HTTPSampler.method">POST</stringProp>
+            <boolProp name="HTTPSampler.follow_redirects">true</boolProp>
+            <boolProp name="HTTPSampler.auto_redirects">false</boolProp>
+            <boolProp name="HTTPSampler.use_keepalive">true</boolProp>
+            <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp>
+            <stringProp name="HTTPSampler.embedded_url_re"></stringProp>
+            <stringProp name="HTTPSampler.connect_timeout"></stringProp>
+            <stringProp name="HTTPSampler.response_timeout"></stringProp>
+          </HTTPSamplerProxy>
+          <hashTree>
+            <ResponseAssertion guiclass="AssertionGui" testclass="ResponseAssertion" testname="Response Assertion" enabled="true">
+              <collectionProp name="Asserion.test_strings">
+                <stringProp name="-1754954177">Stream demo is created.</stringProp>
+              </collectionProp>
+              <stringProp name="Assertion.custom_message"></stringProp>
+              <stringProp name="Assertion.test_field">Assertion.response_data</stringProp>
+              <boolProp name="Assertion.assume_success">true</boolProp>
+              <intProp name="Assertion.test_type">16</intProp>
+            </ResponseAssertion>
+            <hashTree/>
+          </hashTree>
+          <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="API_CreateRule" enabled="true">
+            <boolProp name="HTTPSampler.postBodyRaw">true</boolProp>
+            <elementProp name="HTTPsampler.Arguments" elementType="Arguments">
+              <collectionProp name="Arguments.arguments">
+                <elementProp name="" elementType="HTTPArgument">
+                  <boolProp name="HTTPArgument.always_encode">false</boolProp>
+                  <stringProp name="Argument.value">{&#xd;
+  &quot;id&quot;: &quot;rule1&quot;,&#xd;
+  &quot;sql&quot;: &quot;SELECT * FROM demo WHERE temperature &gt; 30&quot;,&#xd;
+  &quot;actions&quot;: [&#xd;
+    {&#xd;
+      &quot;mqtt&quot;: {&#xd;
+        &quot;server&quot;: &quot;tcp://${mqtt_srv}:1883&quot;,&#xd;
+        &quot;topic&quot;: &quot;devices/result&quot;,&#xd;
+        &quot;qos&quot;: 1,&#xd;
+        &quot;clientId&quot;: &quot;demo_001&quot;&#xd;
+      }&#xd;
+    }&#xd;
+  ]&#xd;
+}</stringProp>
+                  <stringProp name="Argument.metadata">=</stringProp>
+                </elementProp>
+              </collectionProp>
+            </elementProp>
+            <stringProp name="HTTPSampler.domain">${srv}</stringProp>
+            <stringProp name="HTTPSampler.port">${rest_port}</stringProp>
+            <stringProp name="HTTPSampler.protocol"></stringProp>
+            <stringProp name="HTTPSampler.contentEncoding"></stringProp>
+            <stringProp name="HTTPSampler.path">/rules</stringProp>
+            <stringProp name="HTTPSampler.method">POST</stringProp>
+            <boolProp name="HTTPSampler.follow_redirects">true</boolProp>
+            <boolProp name="HTTPSampler.auto_redirects">false</boolProp>
+            <boolProp name="HTTPSampler.use_keepalive">true</boolProp>
+            <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp>
+            <stringProp name="HTTPSampler.embedded_url_re"></stringProp>
+            <stringProp name="HTTPSampler.connect_timeout"></stringProp>
+            <stringProp name="HTTPSampler.response_timeout"></stringProp>
+          </HTTPSamplerProxy>
+          <hashTree>
+            <ResponseAssertion guiclass="AssertionGui" testclass="ResponseAssertion" testname="Response Assertion" enabled="true">
+              <collectionProp name="Asserion.test_strings">
+                <stringProp name="-2022196798">Rule rule1 was created</stringProp>
+              </collectionProp>
+              <stringProp name="Assertion.custom_message"></stringProp>
+              <stringProp name="Assertion.test_field">Assertion.response_data</stringProp>
+              <boolProp name="Assertion.assume_success">true</boolProp>
+              <intProp name="Assertion.test_type">16</intProp>
+            </ResponseAssertion>
+            <hashTree/>
+          </hashTree>
+          <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="API_GetRuleStatus" enabled="true">
+            <boolProp name="HTTPSampler.postBodyRaw">true</boolProp>
+            <elementProp name="HTTPsampler.Arguments" elementType="Arguments">
+              <collectionProp name="Arguments.arguments">
+                <elementProp name="" elementType="HTTPArgument">
+                  <boolProp name="HTTPArgument.always_encode">false</boolProp>
+                  <stringProp name="Argument.value"></stringProp>
+                  <stringProp name="Argument.metadata">=</stringProp>
+                </elementProp>
+              </collectionProp>
+            </elementProp>
+            <stringProp name="HTTPSampler.domain">${srv}</stringProp>
+            <stringProp name="HTTPSampler.port">${rest_port}</stringProp>
+            <stringProp name="HTTPSampler.protocol"></stringProp>
+            <stringProp name="HTTPSampler.contentEncoding"></stringProp>
+            <stringProp name="HTTPSampler.path">/rules/rule1/status</stringProp>
+            <stringProp name="HTTPSampler.method">GET</stringProp>
+            <boolProp name="HTTPSampler.follow_redirects">true</boolProp>
+            <boolProp name="HTTPSampler.auto_redirects">false</boolProp>
+            <boolProp name="HTTPSampler.use_keepalive">true</boolProp>
+            <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp>
+            <stringProp name="HTTPSampler.embedded_url_re"></stringProp>
+            <stringProp name="HTTPSampler.connect_timeout"></stringProp>
+            <stringProp name="HTTPSampler.response_timeout"></stringProp>
+          </HTTPSamplerProxy>
+          <hashTree>
+            <JSONPathAssertion guiclass="JSONPathAssertionGui" testclass="JSONPathAssertion" testname="JSON Assertion" enabled="true">
+              <stringProp name="JSON_PATH">$.source_demo_0_records_in_total</stringProp>
+              <stringProp name="EXPECTED_VALUE">0</stringProp>
+              <boolProp name="JSONVALIDATION">true</boolProp>
+              <boolProp name="EXPECT_NULL">false</boolProp>
+              <boolProp name="INVERT">false</boolProp>
+              <boolProp name="ISREGEX">false</boolProp>
+            </JSONPathAssertion>
+            <hashTree/>
+          </hashTree>
+          <LoopController guiclass="LoopControlPanel" testclass="LoopController" testname="loop_controller" enabled="true">
+            <boolProp name="LoopController.continue_forever">true</boolProp>
+            <stringProp name="LoopController.loops">10</stringProp>
+          </LoopController>
+          <hashTree>
+            <CSVDataSet guiclass="TestBeanGUI" testclass="CSVDataSet" testname="CSV Data Set Config" enabled="true">
+              <stringProp name="delimiter">,</stringProp>
+              <stringProp name="fileEncoding"></stringProp>
+              <stringProp name="filename">iot_data.txt</stringProp>
+              <boolProp name="ignoreFirstLine">false</boolProp>
+              <boolProp name="quotedData">false</boolProp>
+              <boolProp name="recycle">true</boolProp>
+              <stringProp name="shareMode">shareMode.thread</stringProp>
+              <boolProp name="stopThread">false</boolProp>
+              <stringProp name="variableNames">device_id,temperature,humidity</stringProp>
+            </CSVDataSet>
+            <hashTree/>
+            <net.xmeter.samplers.PubSampler guiclass="net.xmeter.gui.PubSamplerUI" testclass="net.xmeter.samplers.PubSampler" testname="MQTT Pub Sampler" enabled="true">
+              <stringProp name="mqtt.topic_name">devices/${device_id}/messages</stringProp>
+              <stringProp name="mqtt.qos_level">0</stringProp>
+              <boolProp name="mqtt.add_timestamp">false</boolProp>
+              <stringProp name="mqtt.message_type">String</stringProp>
+              <stringProp name="mqtt.message_type_fixed_length">1024</stringProp>
+              <stringProp name="mqtt.message_to_sent">{&quot;temperature&quot;: ${temperature}, &quot;humidity&quot; : ${humidity}}</stringProp>
+            </net.xmeter.samplers.PubSampler>
+            <hashTree/>
+          </hashTree>
+          <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="API_GetRuleStatus" enabled="true">
+            <boolProp name="HTTPSampler.postBodyRaw">true</boolProp>
+            <elementProp name="HTTPsampler.Arguments" elementType="Arguments">
+              <collectionProp name="Arguments.arguments">
+                <elementProp name="" elementType="HTTPArgument">
+                  <boolProp name="HTTPArgument.always_encode">false</boolProp>
+                  <stringProp name="Argument.value"></stringProp>
+                  <stringProp name="Argument.metadata">=</stringProp>
+                </elementProp>
+              </collectionProp>
+            </elementProp>
+            <stringProp name="HTTPSampler.domain">${srv}</stringProp>
+            <stringProp name="HTTPSampler.port">${rest_port}</stringProp>
+            <stringProp name="HTTPSampler.protocol"></stringProp>
+            <stringProp name="HTTPSampler.contentEncoding"></stringProp>
+            <stringProp name="HTTPSampler.path">/rules/rule1/status</stringProp>
+            <stringProp name="HTTPSampler.method">GET</stringProp>
+            <boolProp name="HTTPSampler.follow_redirects">true</boolProp>
+            <boolProp name="HTTPSampler.auto_redirects">false</boolProp>
+            <boolProp name="HTTPSampler.use_keepalive">true</boolProp>
+            <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp>
+            <stringProp name="HTTPSampler.embedded_url_re"></stringProp>
+            <stringProp name="HTTPSampler.connect_timeout"></stringProp>
+            <stringProp name="HTTPSampler.response_timeout"></stringProp>
+          </HTTPSamplerProxy>
+          <hashTree>
+            <JSONPathAssertion guiclass="JSONPathAssertionGui" testclass="JSONPathAssertion" testname="JSON Assertion" enabled="true">
+              <stringProp name="JSON_PATH">$.source_demo_0_records_in_total</stringProp>
+              <stringProp name="EXPECTED_VALUE">10</stringProp>
+              <boolProp name="JSONVALIDATION">true</boolProp>
+              <boolProp name="EXPECT_NULL">false</boolProp>
+              <boolProp name="INVERT">false</boolProp>
+              <boolProp name="ISREGEX">false</boolProp>
+            </JSONPathAssertion>
+            <hashTree/>
+            <JSONPathAssertion guiclass="JSONPathAssertionGui" testclass="JSONPathAssertion" testname="JSON Assertion" enabled="false">
+              <stringProp name="JSON_PATH">$.sink_sink_mqtt_0_records_in_total</stringProp>
+              <stringProp name="EXPECTED_VALUE">6</stringProp>
+              <boolProp name="JSONVALIDATION">true</boolProp>
+              <boolProp name="EXPECT_NULL">false</boolProp>
+              <boolProp name="INVERT">false</boolProp>
+              <boolProp name="ISREGEX">false</boolProp>
+            </JSONPathAssertion>
+            <hashTree/>
+            <ConstantTimer guiclass="ConstantTimerGui" testclass="ConstantTimer" testname="Constant Timer" enabled="true">
+              <stringProp name="ConstantTimer.delay">5000</stringProp>
+            </ConstantTimer>
+            <hashTree/>
+          </hashTree>
+          <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="API_DropRule" enabled="true">
+            <boolProp name="HTTPSampler.postBodyRaw">true</boolProp>
+            <elementProp name="HTTPsampler.Arguments" elementType="Arguments">
+              <collectionProp name="Arguments.arguments">
+                <elementProp name="" elementType="HTTPArgument">
+                  <boolProp name="HTTPArgument.always_encode">false</boolProp>
+                  <stringProp name="Argument.value"></stringProp>
+                  <stringProp name="Argument.metadata">=</stringProp>
+                </elementProp>
+              </collectionProp>
+            </elementProp>
+            <stringProp name="HTTPSampler.domain">${srv}</stringProp>
+            <stringProp name="HTTPSampler.port">${rest_port}</stringProp>
+            <stringProp name="HTTPSampler.protocol"></stringProp>
+            <stringProp name="HTTPSampler.contentEncoding"></stringProp>
+            <stringProp name="HTTPSampler.path">/rules/rule1</stringProp>
+            <stringProp name="HTTPSampler.method">DELETE</stringProp>
+            <boolProp name="HTTPSampler.follow_redirects">true</boolProp>
+            <boolProp name="HTTPSampler.auto_redirects">false</boolProp>
+            <boolProp name="HTTPSampler.use_keepalive">true</boolProp>
+            <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp>
+            <stringProp name="HTTPSampler.embedded_url_re"></stringProp>
+            <stringProp name="HTTPSampler.connect_timeout"></stringProp>
+            <stringProp name="HTTPSampler.response_timeout"></stringProp>
+          </HTTPSamplerProxy>
+          <hashTree>
+            <ResponseAssertion guiclass="AssertionGui" testclass="ResponseAssertion" testname="Response Assertion" enabled="true">
+              <collectionProp name="Asserion.test_strings">
+                <stringProp name="717250485">Rule rule1 is dropped.</stringProp>
+              </collectionProp>
+              <stringProp name="Assertion.custom_message"></stringProp>
+              <stringProp name="Assertion.test_field">Assertion.response_data</stringProp>
+              <boolProp name="Assertion.assume_success">false</boolProp>
+              <intProp name="Assertion.test_type">16</intProp>
+            </ResponseAssertion>
+            <hashTree/>
+          </hashTree>
+          <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="API_Drop_Stream" enabled="true">
+            <boolProp name="HTTPSampler.postBodyRaw">true</boolProp>
+            <elementProp name="HTTPsampler.Arguments" elementType="Arguments">
+              <collectionProp name="Arguments.arguments">
+                <elementProp name="" elementType="HTTPArgument">
+                  <boolProp name="HTTPArgument.always_encode">false</boolProp>
+                  <stringProp name="Argument.value"></stringProp>
+                  <stringProp name="Argument.metadata">=</stringProp>
+                </elementProp>
+              </collectionProp>
+            </elementProp>
+            <stringProp name="HTTPSampler.domain">${srv}</stringProp>
+            <stringProp name="HTTPSampler.port">${rest_port}</stringProp>
+            <stringProp name="HTTPSampler.protocol"></stringProp>
+            <stringProp name="HTTPSampler.contentEncoding"></stringProp>
+            <stringProp name="HTTPSampler.path">/streams/demo</stringProp>
+            <stringProp name="HTTPSampler.method">DELETE</stringProp>
+            <boolProp name="HTTPSampler.follow_redirects">true</boolProp>
+            <boolProp name="HTTPSampler.auto_redirects">false</boolProp>
+            <boolProp name="HTTPSampler.use_keepalive">true</boolProp>
+            <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp>
+            <stringProp name="HTTPSampler.embedded_url_re"></stringProp>
+            <stringProp name="HTTPSampler.connect_timeout"></stringProp>
+            <stringProp name="HTTPSampler.response_timeout"></stringProp>
+          </HTTPSamplerProxy>
+          <hashTree>
+            <ResponseAssertion guiclass="AssertionGui" testclass="ResponseAssertion" testname="Response Assertion" enabled="true">
+              <collectionProp name="Asserion.test_strings">
+                <stringProp name="287881319">Stream demo is dropped.</stringProp>
+              </collectionProp>
+              <stringProp name="Assertion.custom_message"></stringProp>
+              <stringProp name="Assertion.test_field">Assertion.response_data</stringProp>
+              <boolProp name="Assertion.assume_success">false</boolProp>
+              <intProp name="Assertion.test_type">16</intProp>
+            </ResponseAssertion>
+            <hashTree/>
+          </hashTree>
+          <ConstantTimer guiclass="ConstantTimerGui" testclass="ConstantTimer" testname="Constant Timer" enabled="true">
+            <stringProp name="ConstantTimer.delay">500</stringProp>
+          </ConstantTimer>
+          <hashTree/>
+        </hashTree>
+      </hashTree>
+      <ThreadGroup guiclass="ThreadGroupGui" testclass="ThreadGroup" testname="Result" enabled="true">
+        <stringProp name="ThreadGroup.on_sample_error">continue</stringProp>
+        <elementProp name="ThreadGroup.main_controller" elementType="LoopController" guiclass="LoopControlPanel" testclass="LoopController" testname="Loop Controller" enabled="true">
+          <boolProp name="LoopController.continue_forever">false</boolProp>
+          <stringProp name="LoopController.loops">6</stringProp>
+        </elementProp>
+        <stringProp name="ThreadGroup.num_threads">1</stringProp>
+        <stringProp name="ThreadGroup.ramp_time">1</stringProp>
+        <boolProp name="ThreadGroup.scheduler">false</boolProp>
+        <stringProp name="ThreadGroup.duration"></stringProp>
+        <stringProp name="ThreadGroup.delay"></stringProp>
+      </ThreadGroup>
+      <hashTree>
+        <ResultCollector guiclass="ViewResultsFullVisualizer" testclass="ResultCollector" testname="View Results Tree" enabled="true">
+          <boolProp name="ResultCollector.error_logging">false</boolProp>
+          <objProp>
+            <name>saveConfig</name>
+            <value class="SampleSaveConfiguration">
+              <time>true</time>
+              <latency>true</latency>
+              <timestamp>true</timestamp>
+              <success>true</success>
+              <label>true</label>
+              <code>true</code>
+              <message>true</message>
+              <threadName>true</threadName>
+              <dataType>true</dataType>
+              <encoding>false</encoding>
+              <assertions>true</assertions>
+              <subresults>true</subresults>
+              <responseData>false</responseData>
+              <samplerData>false</samplerData>
+              <xml>false</xml>
+              <fieldNames>true</fieldNames>
+              <responseHeaders>false</responseHeaders>
+              <requestHeaders>false</requestHeaders>
+              <responseDataOnError>false</responseDataOnError>
+              <saveAssertionResultsFailureMessage>true</saveAssertionResultsFailureMessage>
+              <assertionsResultsToSave>0</assertionsResultsToSave>
+              <bytes>true</bytes>
+              <sentBytes>true</sentBytes>
+              <threadCounts>true</threadCounts>
+              <idleTime>true</idleTime>
+              <connectTime>true</connectTime>
+            </value>
+          </objProp>
+          <stringProp name="filename"></stringProp>
+        </ResultCollector>
+        <hashTree/>
+        <OnceOnlyController guiclass="OnceOnlyControllerGui" testclass="OnceOnlyController" testname="Once Only Controller" enabled="true"/>
+        <hashTree>
+          <net.xmeter.samplers.ConnectSampler guiclass="net.xmeter.gui.ConnectSamplerUI" testclass="net.xmeter.samplers.ConnectSampler" testname="MQTT Connect" enabled="true">
+            <stringProp name="mqtt.server">${mqtt_srv}</stringProp>
+            <stringProp name="mqtt.port">1883</stringProp>
+            <stringProp name="mqtt.version">3.1</stringProp>
+            <stringProp name="mqtt.conn_timeout">10</stringProp>
+            <boolProp name="mqtt.private_protocol">false</boolProp>
+            <stringProp name="mqtt.listener_timeout">10</stringProp>
+            <stringProp name="mqtt.protocol">TCP</stringProp>
+            <boolProp name="mqtt.dual_ssl_authentication">false</boolProp>
+            <stringProp name="mqtt.keystore_file_path"></stringProp>
+            <stringProp name="mqtt.keystore_password"></stringProp>
+            <stringProp name="mqtt.clientcert_file_path"></stringProp>
+            <stringProp name="mqtt.clientcert_password"></stringProp>
+            <stringProp name="mqtt.user_name"></stringProp>
+            <stringProp name="mqtt.password"></stringProp>
+            <stringProp name="mqtt.client_id_prefix">conn_</stringProp>
+            <boolProp name="mqtt.client_id_suffix">true</boolProp>
+            <stringProp name="mqtt.conn_keep_alive">300</stringProp>
+            <stringProp name="mqtt.conn_attampt_max">0</stringProp>
+            <stringProp name="mqtt.reconn_attampt_max">0</stringProp>
+          </net.xmeter.samplers.ConnectSampler>
+          <hashTree/>
+        </hashTree>
+        <net.xmeter.samplers.SubSampler guiclass="net.xmeter.gui.SubSamplerUI" testclass="net.xmeter.samplers.SubSampler" testname="AnalysisResult" enabled="true">
+          <stringProp name="mqtt.topic_name">devices/result</stringProp>
+          <stringProp name="mqtt.qos_level">0</stringProp>
+          <boolProp name="mqtt.add_timestamp">false</boolProp>
+          <boolProp name="mqtt.debug_response">true</boolProp>
+          <stringProp name="mqtt.sample_condition">number of received messages</stringProp>
+          <stringProp name="mqtt.sample_condition_value">1</stringProp>
+        </net.xmeter.samplers.SubSampler>
+        <hashTree>
+          <JSONPathAssertion guiclass="JSONPathAssertionGui" testclass="JSONPathAssertion" testname="temperature Assertion" enabled="true">
+            <stringProp name="JSON_PATH">$[0].temperature</stringProp>
+            <stringProp name="EXPECTED_VALUE">${temperature}</stringProp>
+            <boolProp name="JSONVALIDATION">true</boolProp>
+            <boolProp name="EXPECT_NULL">false</boolProp>
+            <boolProp name="INVERT">false</boolProp>
+            <boolProp name="ISREGEX">false</boolProp>
+          </JSONPathAssertion>
+          <hashTree/>
+          <JSONPathAssertion guiclass="JSONPathAssertionGui" testclass="JSONPathAssertion" testname="humidity Assertion" enabled="true">
+            <stringProp name="JSON_PATH">$[0].humidity</stringProp>
+            <stringProp name="EXPECTED_VALUE">${humidity}</stringProp>
+            <boolProp name="JSONVALIDATION">true</boolProp>
+            <boolProp name="EXPECT_NULL">false</boolProp>
+            <boolProp name="INVERT">false</boolProp>
+            <boolProp name="ISREGEX">false</boolProp>
+          </JSONPathAssertion>
+          <hashTree/>
+        </hashTree>
+        <CSVDataSet guiclass="TestBeanGUI" testclass="CSVDataSet" testname="CSV Data Set Config" enabled="true">
+          <stringProp name="delimiter">,</stringProp>
+          <stringProp name="fileEncoding"></stringProp>
+          <stringProp name="filename">select_condition_iot_data.txt</stringProp>
+          <boolProp name="ignoreFirstLine">false</boolProp>
+          <boolProp name="quotedData">false</boolProp>
+          <boolProp name="recycle">true</boolProp>
+          <stringProp name="shareMode">shareMode.thread</stringProp>
+          <boolProp name="stopThread">false</boolProp>
+          <stringProp name="variableNames">device_id,temperature,humidity</stringProp>
+        </CSVDataSet>
+        <hashTree/>
+      </hashTree>
+    </hashTree>
+  </hashTree>
+</jmeterTestPlan>

+ 26 - 0
fvt_scripts/setup_env.sh

@@ -0,0 +1,26 @@
+#!/bin/bash
+
+emqx_ids=`ps aux|grep "emqx" | grep "/usr/bin"|awk '{printf $2 " "}'`
+if [ "$emqx_ids" = "" ] ; then
+  echo "No emqx broker was started"
+  echo "starting emqx..."
+  emqx start
+else
+  echo "emqx has already started"
+  #for pid in $emqx_ids ; do
+    #echo "kill emqx: " $pid
+    #kill -9 $pid
+  #done
+fi
+
+pids=`ps aux|grep "server" | grep "bin"|awk '{printf $2 " "}'`
+if [ "$pids" = "" ] ; then
+   echo "No kuiper server was started"
+else
+  for pid in $pids ; do
+    echo "kill kuiper " $pid
+    kill -9 $pid
+  done
+fi
+
+fvt_scripts/start_kuiper.sh

+ 15 - 0
fvt_scripts/start_kuiper.sh

@@ -0,0 +1,15 @@
+#!/bin/bash
+
+ver=`git describe --tags --always`
+os=`uname -s | tr "[A-Z]" "[a-z]"`
+base_dir=_build/kuiper-"$ver"-"$os"-x86_64
+
+rm -rf $base_dir/data/*
+ls -l $base_dir/bin/server
+
+echo "starting kuiper at " $base_dir
+cd $base_dir/
+touch log/kuiper.out
+export BUILD_ID=dontKillMe
+nohup bin/server > log/kuiper.out 2>&1 &
+

+ 663 - 0
fvt_scripts/streams_test.jmx

@@ -0,0 +1,663 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<jmeterTestPlan version="1.2" properties="4.0" jmeter="4.0 r1823414">
+  <hashTree>
+    <TestPlan guiclass="TestPlanGui" testclass="TestPlan" testname="Test Plan" enabled="true">
+      <stringProp name="TestPlan.comments"></stringProp>
+      <boolProp name="TestPlan.functional_mode">false</boolProp>
+      <boolProp name="TestPlan.tearDown_on_shutdown">true</boolProp>
+      <boolProp name="TestPlan.serialize_threadgroups">false</boolProp>
+      <elementProp name="TestPlan.user_defined_variables" elementType="Arguments" guiclass="ArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true">
+        <collectionProp name="Arguments.arguments"/>
+      </elementProp>
+      <stringProp name="TestPlan.user_define_classpath"></stringProp>
+    </TestPlan>
+    <hashTree>
+      <Arguments guiclass="ArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true">
+        <collectionProp name="Arguments.arguments">
+          <elementProp name="srv" elementType="Argument">
+            <stringProp name="Argument.name">srv</stringProp>
+            <stringProp name="Argument.value">127.0.0.1</stringProp>
+            <stringProp name="Argument.metadata">=</stringProp>
+          </elementProp>
+          <elementProp name="rest_port" elementType="Argument">
+            <stringProp name="Argument.name">rest_port</stringProp>
+            <stringProp name="Argument.value">9081</stringProp>
+            <stringProp name="Argument.metadata">=</stringProp>
+          </elementProp>
+          <elementProp name="k_home" elementType="Argument">
+            <stringProp name="Argument.name">k_home</stringProp>
+            <stringProp name="Argument.value">${__property(base,,)}</stringProp>
+            <stringProp name="Argument.metadata">=</stringProp>
+          </elementProp>
+        </collectionProp>
+      </Arguments>
+      <hashTree/>
+      <ThreadGroup guiclass="ThreadGroupGui" testclass="ThreadGroup" testname="Stream_API" enabled="true">
+        <stringProp name="ThreadGroup.on_sample_error">continue</stringProp>
+        <elementProp name="ThreadGroup.main_controller" elementType="LoopController" guiclass="LoopControlPanel" testclass="LoopController" testname="Loop Controller" enabled="true">
+          <boolProp name="LoopController.continue_forever">false</boolProp>
+          <stringProp name="LoopController.loops">1</stringProp>
+        </elementProp>
+        <stringProp name="ThreadGroup.num_threads">1</stringProp>
+        <stringProp name="ThreadGroup.ramp_time">1</stringProp>
+        <boolProp name="ThreadGroup.scheduler">false</boolProp>
+        <stringProp name="ThreadGroup.duration"></stringProp>
+        <stringProp name="ThreadGroup.delay"></stringProp>
+      </ThreadGroup>
+      <hashTree>
+        <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Get_Empty_Streams" enabled="true">
+          <elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true">
+            <collectionProp name="Arguments.arguments"/>
+          </elementProp>
+          <stringProp name="HTTPSampler.domain">${srv}</stringProp>
+          <stringProp name="HTTPSampler.port">${rest_port}</stringProp>
+          <stringProp name="HTTPSampler.protocol"></stringProp>
+          <stringProp name="HTTPSampler.contentEncoding"></stringProp>
+          <stringProp name="HTTPSampler.path">/streams</stringProp>
+          <stringProp name="HTTPSampler.method">GET</stringProp>
+          <boolProp name="HTTPSampler.follow_redirects">true</boolProp>
+          <boolProp name="HTTPSampler.auto_redirects">false</boolProp>
+          <boolProp name="HTTPSampler.use_keepalive">true</boolProp>
+          <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp>
+          <stringProp name="HTTPSampler.embedded_url_re"></stringProp>
+          <stringProp name="HTTPSampler.connect_timeout"></stringProp>
+          <stringProp name="HTTPSampler.response_timeout"></stringProp>
+        </HTTPSamplerProxy>
+        <hashTree>
+          <JSONPathAssertion guiclass="JSONPathAssertionGui" testclass="JSONPathAssertion" testname="JSON Assertion" enabled="true">
+            <stringProp name="JSON_PATH">$</stringProp>
+            <stringProp name="EXPECTED_VALUE">[]</stringProp>
+            <boolProp name="JSONVALIDATION">false</boolProp>
+            <boolProp name="EXPECT_NULL">false</boolProp>
+            <boolProp name="INVERT">false</boolProp>
+            <boolProp name="ISREGEX">true</boolProp>
+          </JSONPathAssertion>
+          <hashTree/>
+        </hashTree>
+        <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="CreateStream" enabled="true">
+          <boolProp name="HTTPSampler.postBodyRaw">true</boolProp>
+          <elementProp name="HTTPsampler.Arguments" elementType="Arguments">
+            <collectionProp name="Arguments.arguments">
+              <elementProp name="" elementType="HTTPArgument">
+                <boolProp name="HTTPArgument.always_encode">false</boolProp>
+                <stringProp name="Argument.value">{&#xd;
+&quot;sql&quot; : &quot;create stream my_stream (USERID BIGINT, FIRST_NAME STRING, LAST_NAME STRING, NICKNAMES ARRAY(STRING), Gender BOOLEAN, ADDRESS STRUCT(STREET_NAME STRING, NUMBER BIGINT),) WITH ( datasource = \&quot;topic/test\&quot;, FORMAT = \&quot;json\&quot;, KEY = \&quot;id\&quot;)&quot;&#xd;
+}</stringProp>
+                <stringProp name="Argument.metadata">=</stringProp>
+              </elementProp>
+            </collectionProp>
+          </elementProp>
+          <stringProp name="HTTPSampler.domain">${srv}</stringProp>
+          <stringProp name="HTTPSampler.port">${rest_port}</stringProp>
+          <stringProp name="HTTPSampler.protocol"></stringProp>
+          <stringProp name="HTTPSampler.contentEncoding"></stringProp>
+          <stringProp name="HTTPSampler.path">/streams</stringProp>
+          <stringProp name="HTTPSampler.method">POST</stringProp>
+          <boolProp name="HTTPSampler.follow_redirects">true</boolProp>
+          <boolProp name="HTTPSampler.auto_redirects">false</boolProp>
+          <boolProp name="HTTPSampler.use_keepalive">true</boolProp>
+          <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp>
+          <stringProp name="HTTPSampler.embedded_url_re"></stringProp>
+          <stringProp name="HTTPSampler.connect_timeout"></stringProp>
+          <stringProp name="HTTPSampler.response_timeout"></stringProp>
+        </HTTPSamplerProxy>
+        <hashTree>
+          <ResponseAssertion guiclass="AssertionGui" testclass="ResponseAssertion" testname="Response Assertion" enabled="true">
+            <collectionProp name="Asserion.test_strings">
+              <stringProp name="-1283107633">Stream my_stream is created.</stringProp>
+            </collectionProp>
+            <stringProp name="Assertion.custom_message"></stringProp>
+            <stringProp name="Assertion.test_field">Assertion.response_data</stringProp>
+            <boolProp name="Assertion.assume_success">false</boolProp>
+            <intProp name="Assertion.test_type">16</intProp>
+          </ResponseAssertion>
+          <hashTree/>
+        </hashTree>
+        <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="CreateDuplicatedStream" enabled="true">
+          <boolProp name="HTTPSampler.postBodyRaw">true</boolProp>
+          <elementProp name="HTTPsampler.Arguments" elementType="Arguments">
+            <collectionProp name="Arguments.arguments">
+              <elementProp name="" elementType="HTTPArgument">
+                <boolProp name="HTTPArgument.always_encode">false</boolProp>
+                <stringProp name="Argument.value">{&#xd;
+&quot;sql&quot; : &quot;create stream my_stream (USERID BIGINT, FIRST_NAME STRING, LAST_NAME STRING, NICKNAMES ARRAY(STRING), Gender BOOLEAN, ADDRESS STRUCT(STREET_NAME STRING, NUMBER BIGINT),) WITH ( datasource = \&quot;topic/test\&quot;, FORMAT = \&quot;json\&quot;, KEY = \&quot;id\&quot;)&quot;&#xd;
+}</stringProp>
+                <stringProp name="Argument.metadata">=</stringProp>
+              </elementProp>
+            </collectionProp>
+          </elementProp>
+          <stringProp name="HTTPSampler.domain">${srv}</stringProp>
+          <stringProp name="HTTPSampler.port">${rest_port}</stringProp>
+          <stringProp name="HTTPSampler.protocol"></stringProp>
+          <stringProp name="HTTPSampler.contentEncoding"></stringProp>
+          <stringProp name="HTTPSampler.path">/streams</stringProp>
+          <stringProp name="HTTPSampler.method">POST</stringProp>
+          <boolProp name="HTTPSampler.follow_redirects">true</boolProp>
+          <boolProp name="HTTPSampler.auto_redirects">false</boolProp>
+          <boolProp name="HTTPSampler.use_keepalive">true</boolProp>
+          <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp>
+          <stringProp name="HTTPSampler.embedded_url_re"></stringProp>
+          <stringProp name="HTTPSampler.connect_timeout"></stringProp>
+          <stringProp name="HTTPSampler.response_timeout"></stringProp>
+        </HTTPSamplerProxy>
+        <hashTree>
+          <ResponseAssertion guiclass="AssertionGui" testclass="ResponseAssertion" testname="Response Assertion" enabled="true">
+            <collectionProp name="Asserion.test_strings">
+              <stringProp name="51508">400</stringProp>
+            </collectionProp>
+            <stringProp name="Assertion.custom_message"></stringProp>
+            <stringProp name="Assertion.test_field">Assertion.response_code</stringProp>
+            <boolProp name="Assertion.assume_success">true</boolProp>
+            <intProp name="Assertion.test_type">16</intProp>
+          </ResponseAssertion>
+          <hashTree/>
+        </hashTree>
+        <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Get_Streams" enabled="true">
+          <elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true">
+            <collectionProp name="Arguments.arguments"/>
+          </elementProp>
+          <stringProp name="HTTPSampler.domain">${srv}</stringProp>
+          <stringProp name="HTTPSampler.port">${rest_port}</stringProp>
+          <stringProp name="HTTPSampler.protocol"></stringProp>
+          <stringProp name="HTTPSampler.contentEncoding"></stringProp>
+          <stringProp name="HTTPSampler.path">/streams</stringProp>
+          <stringProp name="HTTPSampler.method">GET</stringProp>
+          <boolProp name="HTTPSampler.follow_redirects">true</boolProp>
+          <boolProp name="HTTPSampler.auto_redirects">false</boolProp>
+          <boolProp name="HTTPSampler.use_keepalive">true</boolProp>
+          <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp>
+          <stringProp name="HTTPSampler.embedded_url_re"></stringProp>
+          <stringProp name="HTTPSampler.connect_timeout"></stringProp>
+          <stringProp name="HTTPSampler.response_timeout"></stringProp>
+        </HTTPSamplerProxy>
+        <hashTree>
+          <ResponseAssertion guiclass="AssertionGui" testclass="ResponseAssertion" testname="Response Assertion" enabled="true">
+            <collectionProp name="Asserion.test_strings">
+              <stringProp name="-1495874189">my_stream</stringProp>
+            </collectionProp>
+            <stringProp name="Assertion.custom_message"></stringProp>
+            <stringProp name="Assertion.test_field">Assertion.response_data</stringProp>
+            <boolProp name="Assertion.assume_success">false</boolProp>
+            <intProp name="Assertion.test_type">16</intProp>
+          </ResponseAssertion>
+          <hashTree/>
+        </hashTree>
+        <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Describe_Stream" enabled="true">
+          <elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true">
+            <collectionProp name="Arguments.arguments"/>
+          </elementProp>
+          <stringProp name="HTTPSampler.domain">${srv}</stringProp>
+          <stringProp name="HTTPSampler.port">${rest_port}</stringProp>
+          <stringProp name="HTTPSampler.protocol"></stringProp>
+          <stringProp name="HTTPSampler.contentEncoding"></stringProp>
+          <stringProp name="HTTPSampler.path">/streams/my_stream</stringProp>
+          <stringProp name="HTTPSampler.method">GET</stringProp>
+          <boolProp name="HTTPSampler.follow_redirects">true</boolProp>
+          <boolProp name="HTTPSampler.auto_redirects">false</boolProp>
+          <boolProp name="HTTPSampler.use_keepalive">true</boolProp>
+          <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp>
+          <stringProp name="HTTPSampler.embedded_url_re"></stringProp>
+          <stringProp name="HTTPSampler.connect_timeout"></stringProp>
+          <stringProp name="HTTPSampler.response_timeout"></stringProp>
+        </HTTPSamplerProxy>
+        <hashTree>
+          <ResponseAssertion guiclass="AssertionGui" testclass="ResponseAssertion" testname="Response Assertion" enabled="true">
+            <collectionProp name="Asserion.test_strings">
+              <stringProp name="-1495874189">my_stream</stringProp>
+            </collectionProp>
+            <stringProp name="Assertion.custom_message"></stringProp>
+            <stringProp name="Assertion.test_field">Assertion.response_data</stringProp>
+            <boolProp name="Assertion.assume_success">false</boolProp>
+            <intProp name="Assertion.test_type">16</intProp>
+          </ResponseAssertion>
+          <hashTree/>
+          <ResponseAssertion guiclass="AssertionGui" testclass="ResponseAssertion" testname="Response Assertion" enabled="true">
+            <collectionProp name="Asserion.test_strings">
+              <stringProp name="353659610">FIRST_NAME</stringProp>
+            </collectionProp>
+            <stringProp name="Assertion.custom_message"></stringProp>
+            <stringProp name="Assertion.test_field">Assertion.response_data</stringProp>
+            <boolProp name="Assertion.assume_success">false</boolProp>
+            <intProp name="Assertion.test_type">16</intProp>
+          </ResponseAssertion>
+          <hashTree/>
+        </hashTree>
+        <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Drop_Stream" enabled="true">
+          <boolProp name="HTTPSampler.postBodyRaw">true</boolProp>
+          <elementProp name="HTTPsampler.Arguments" elementType="Arguments">
+            <collectionProp name="Arguments.arguments">
+              <elementProp name="" elementType="HTTPArgument">
+                <boolProp name="HTTPArgument.always_encode">false</boolProp>
+                <stringProp name="Argument.value"></stringProp>
+                <stringProp name="Argument.metadata">=</stringProp>
+              </elementProp>
+            </collectionProp>
+          </elementProp>
+          <stringProp name="HTTPSampler.domain">${srv}</stringProp>
+          <stringProp name="HTTPSampler.port">${rest_port}</stringProp>
+          <stringProp name="HTTPSampler.protocol"></stringProp>
+          <stringProp name="HTTPSampler.contentEncoding"></stringProp>
+          <stringProp name="HTTPSampler.path">/streams/my_stream</stringProp>
+          <stringProp name="HTTPSampler.method">DELETE</stringProp>
+          <boolProp name="HTTPSampler.follow_redirects">true</boolProp>
+          <boolProp name="HTTPSampler.auto_redirects">false</boolProp>
+          <boolProp name="HTTPSampler.use_keepalive">true</boolProp>
+          <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp>
+          <stringProp name="HTTPSampler.embedded_url_re"></stringProp>
+          <stringProp name="HTTPSampler.connect_timeout"></stringProp>
+          <stringProp name="HTTPSampler.response_timeout"></stringProp>
+        </HTTPSamplerProxy>
+        <hashTree>
+          <ResponseAssertion guiclass="AssertionGui" testclass="ResponseAssertion" testname="Response Assertion" enabled="true">
+            <collectionProp name="Asserion.test_strings">
+              <stringProp name="759727863">Stream my_stream is dropped.</stringProp>
+            </collectionProp>
+            <stringProp name="Assertion.custom_message"></stringProp>
+            <stringProp name="Assertion.test_field">Assertion.response_data</stringProp>
+            <boolProp name="Assertion.assume_success">false</boolProp>
+            <intProp name="Assertion.test_type">16</intProp>
+          </ResponseAssertion>
+          <hashTree/>
+        </hashTree>
+        <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Get_Empty_Streams" enabled="true">
+          <elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true">
+            <collectionProp name="Arguments.arguments"/>
+          </elementProp>
+          <stringProp name="HTTPSampler.domain">${srv}</stringProp>
+          <stringProp name="HTTPSampler.port">${rest_port}</stringProp>
+          <stringProp name="HTTPSampler.protocol"></stringProp>
+          <stringProp name="HTTPSampler.contentEncoding"></stringProp>
+          <stringProp name="HTTPSampler.path">/streams</stringProp>
+          <stringProp name="HTTPSampler.method">GET</stringProp>
+          <boolProp name="HTTPSampler.follow_redirects">true</boolProp>
+          <boolProp name="HTTPSampler.auto_redirects">false</boolProp>
+          <boolProp name="HTTPSampler.use_keepalive">true</boolProp>
+          <boolProp name="HTTPSampler.DO_MULTIPART_POST">false</boolProp>
+          <stringProp name="HTTPSampler.embedded_url_re"></stringProp>
+          <stringProp name="HTTPSampler.connect_timeout"></stringProp>
+          <stringProp name="HTTPSampler.response_timeout"></stringProp>
+        </HTTPSamplerProxy>
+        <hashTree>
+          <JSONPathAssertion guiclass="JSONPathAssertionGui" testclass="JSONPathAssertion" testname="JSON Assertion" enabled="true">
+            <stringProp name="JSON_PATH">$</stringProp>
+            <stringProp name="EXPECTED_VALUE">[]</stringProp>
+            <boolProp name="JSONVALIDATION">false</boolProp>
+            <boolProp name="EXPECT_NULL">false</boolProp>
+            <boolProp name="INVERT">false</boolProp>
+            <boolProp name="ISREGEX">true</boolProp>
+          </JSONPathAssertion>
+          <hashTree/>
+        </hashTree>
+        <ConstantTimer guiclass="ConstantTimerGui" testclass="ConstantTimer" testname="Constant Timer" enabled="true">
+          <stringProp name="ConstantTimer.delay">500</stringProp>
+        </ConstantTimer>
+        <hashTree/>
+      </hashTree>
+      <ResultCollector guiclass="ViewResultsFullVisualizer" testclass="ResultCollector" testname="View Results Tree" enabled="true">
+        <boolProp name="ResultCollector.error_logging">false</boolProp>
+        <objProp>
+          <name>saveConfig</name>
+          <value class="SampleSaveConfiguration">
+            <time>true</time>
+            <latency>true</latency>
+            <timestamp>true</timestamp>
+            <success>true</success>
+            <label>true</label>
+            <code>true</code>
+            <message>true</message>
+            <threadName>true</threadName>
+            <dataType>true</dataType>
+            <encoding>false</encoding>
+            <assertions>true</assertions>
+            <subresults>true</subresults>
+            <responseData>false</responseData>
+            <samplerData>false</samplerData>
+            <xml>false</xml>
+            <fieldNames>true</fieldNames>
+            <responseHeaders>false</responseHeaders>
+            <requestHeaders>false</requestHeaders>
+            <responseDataOnError>false</responseDataOnError>
+            <saveAssertionResultsFailureMessage>true</saveAssertionResultsFailureMessage>
+            <assertionsResultsToSave>0</assertionsResultsToSave>
+            <bytes>true</bytes>
+            <sentBytes>true</sentBytes>
+            <threadCounts>true</threadCounts>
+            <idleTime>true</idleTime>
+            <connectTime>true</connectTime>
+          </value>
+        </objProp>
+        <stringProp name="filename"></stringProp>
+      </ResultCollector>
+      <hashTree/>
+      <ThreadGroup guiclass="ThreadGroupGui" testclass="ThreadGroup" testname="CLI" enabled="true">
+        <stringProp name="ThreadGroup.on_sample_error">continue</stringProp>
+        <elementProp name="ThreadGroup.main_controller" elementType="LoopController" guiclass="LoopControlPanel" testclass="LoopController" testname="Loop Controller" enabled="true">
+          <boolProp name="LoopController.continue_forever">false</boolProp>
+          <stringProp name="LoopController.loops">1</stringProp>
+        </elementProp>
+        <stringProp name="ThreadGroup.num_threads">1</stringProp>
+        <stringProp name="ThreadGroup.ramp_time">1</stringProp>
+        <boolProp name="ThreadGroup.scheduler">false</boolProp>
+        <stringProp name="ThreadGroup.duration"></stringProp>
+        <stringProp name="ThreadGroup.delay"></stringProp>
+      </ThreadGroup>
+      <hashTree>
+        <SystemSampler guiclass="SystemSamplerGui" testclass="SystemSampler" testname="Get_Empty_Streams" enabled="true">
+          <boolProp name="SystemSampler.checkReturnCode">false</boolProp>
+          <stringProp name="SystemSampler.expectedReturnCode">0</stringProp>
+          <stringProp name="SystemSampler.command">bin/cli</stringProp>
+          <elementProp name="SystemSampler.arguments" elementType="Arguments" guiclass="ArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true">
+            <collectionProp name="Arguments.arguments">
+              <elementProp name="" elementType="Argument">
+                <stringProp name="Argument.name"></stringProp>
+                <stringProp name="Argument.value">show</stringProp>
+                <stringProp name="Argument.metadata">=</stringProp>
+              </elementProp>
+              <elementProp name="" elementType="Argument">
+                <stringProp name="Argument.name"></stringProp>
+                <stringProp name="Argument.value">streams</stringProp>
+                <stringProp name="Argument.metadata">=</stringProp>
+              </elementProp>
+            </collectionProp>
+          </elementProp>
+          <elementProp name="SystemSampler.environment" elementType="Arguments" guiclass="ArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true">
+            <collectionProp name="Arguments.arguments"/>
+          </elementProp>
+          <stringProp name="SystemSampler.directory">${k_home}</stringProp>
+        </SystemSampler>
+        <hashTree>
+          <ResponseAssertion guiclass="AssertionGui" testclass="ResponseAssertion" testname="Response Assertion" enabled="true">
+            <collectionProp name="Asserion.test_strings">
+              <stringProp name="-222768811">No stream definitions are found</stringProp>
+            </collectionProp>
+            <stringProp name="Assertion.custom_message"></stringProp>
+            <stringProp name="Assertion.test_field">Assertion.response_data</stringProp>
+            <boolProp name="Assertion.assume_success">false</boolProp>
+            <intProp name="Assertion.test_type">16</intProp>
+          </ResponseAssertion>
+          <hashTree/>
+        </hashTree>
+        <SystemSampler guiclass="SystemSamplerGui" testclass="SystemSampler" testname="Create_Stream" enabled="true">
+          <boolProp name="SystemSampler.checkReturnCode">false</boolProp>
+          <stringProp name="SystemSampler.expectedReturnCode">0</stringProp>
+          <stringProp name="SystemSampler.command">bin/cli</stringProp>
+          <elementProp name="SystemSampler.arguments" elementType="Arguments" guiclass="ArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true">
+            <collectionProp name="Arguments.arguments">
+              <elementProp name="" elementType="Argument">
+                <stringProp name="Argument.name"></stringProp>
+                <stringProp name="Argument.value">create</stringProp>
+                <stringProp name="Argument.metadata">=</stringProp>
+              </elementProp>
+              <elementProp name="" elementType="Argument">
+                <stringProp name="Argument.name"></stringProp>
+                <stringProp name="Argument.value">stream</stringProp>
+                <stringProp name="Argument.metadata">=</stringProp>
+              </elementProp>
+              <elementProp name="" elementType="Argument">
+                <stringProp name="Argument.name"></stringProp>
+                <stringProp name="Argument.value">demo</stringProp>
+                <stringProp name="Argument.metadata">=</stringProp>
+              </elementProp>
+              <elementProp name="" elementType="Argument">
+                <stringProp name="Argument.name"></stringProp>
+                <stringProp name="Argument.value">(temperature float, humidity bigint) WITH (FORMAT=&quot;JSON&quot;, DATASOURCE=&quot;devices/+/messages&quot;)</stringProp>
+                <stringProp name="Argument.metadata">=</stringProp>
+              </elementProp>
+            </collectionProp>
+          </elementProp>
+          <elementProp name="SystemSampler.environment" elementType="Arguments" guiclass="ArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true">
+            <collectionProp name="Arguments.arguments"/>
+          </elementProp>
+          <stringProp name="SystemSampler.directory">${k_home}</stringProp>
+        </SystemSampler>
+        <hashTree>
+          <ResponseAssertion guiclass="AssertionGui" testclass="ResponseAssertion" testname="Response Assertion" enabled="true">
+            <collectionProp name="Asserion.test_strings">
+              <stringProp name="-1754954177">Stream demo is created.</stringProp>
+            </collectionProp>
+            <stringProp name="Assertion.custom_message"></stringProp>
+            <stringProp name="Assertion.test_field">Assertion.response_data</stringProp>
+            <boolProp name="Assertion.assume_success">false</boolProp>
+            <intProp name="Assertion.test_type">16</intProp>
+          </ResponseAssertion>
+          <hashTree/>
+        </hashTree>
+        <SystemSampler guiclass="SystemSamplerGui" testclass="SystemSampler" testname="Create_Duplicated_Stream" enabled="true">
+          <boolProp name="SystemSampler.checkReturnCode">false</boolProp>
+          <stringProp name="SystemSampler.expectedReturnCode">0</stringProp>
+          <stringProp name="SystemSampler.command">bin/cli</stringProp>
+          <elementProp name="SystemSampler.arguments" elementType="Arguments" guiclass="ArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true">
+            <collectionProp name="Arguments.arguments">
+              <elementProp name="" elementType="Argument">
+                <stringProp name="Argument.name"></stringProp>
+                <stringProp name="Argument.value">create</stringProp>
+                <stringProp name="Argument.metadata">=</stringProp>
+              </elementProp>
+              <elementProp name="" elementType="Argument">
+                <stringProp name="Argument.name"></stringProp>
+                <stringProp name="Argument.value">stream</stringProp>
+                <stringProp name="Argument.metadata">=</stringProp>
+              </elementProp>
+              <elementProp name="" elementType="Argument">
+                <stringProp name="Argument.name"></stringProp>
+                <stringProp name="Argument.value">demo</stringProp>
+                <stringProp name="Argument.metadata">=</stringProp>
+              </elementProp>
+              <elementProp name="" elementType="Argument">
+                <stringProp name="Argument.name"></stringProp>
+                <stringProp name="Argument.value">(temperature float, humidity bigint) WITH (FORMAT=&quot;JSON&quot;, DATASOURCE=&quot;devices/+/messages&quot;)</stringProp>
+                <stringProp name="Argument.metadata">=</stringProp>
+              </elementProp>
+            </collectionProp>
+          </elementProp>
+          <elementProp name="SystemSampler.environment" elementType="Arguments" guiclass="ArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true">
+            <collectionProp name="Arguments.arguments"/>
+          </elementProp>
+          <stringProp name="SystemSampler.directory">${k_home}</stringProp>
+        </SystemSampler>
+        <hashTree>
+          <ResponseAssertion guiclass="AssertionGui" testclass="ResponseAssertion" testname="Response Assertion" enabled="true">
+            <collectionProp name="Asserion.test_strings">
+              <stringProp name="1756800148">Item demo already exists</stringProp>
+            </collectionProp>
+            <stringProp name="Assertion.custom_message"></stringProp>
+            <stringProp name="Assertion.test_field">Assertion.response_data</stringProp>
+            <boolProp name="Assertion.assume_success">false</boolProp>
+            <intProp name="Assertion.test_type">16</intProp>
+          </ResponseAssertion>
+          <hashTree/>
+        </hashTree>
+        <SystemSampler guiclass="SystemSamplerGui" testclass="SystemSampler" testname="Get_Streams" enabled="true">
+          <boolProp name="SystemSampler.checkReturnCode">false</boolProp>
+          <stringProp name="SystemSampler.expectedReturnCode">0</stringProp>
+          <stringProp name="SystemSampler.command">bin/cli</stringProp>
+          <elementProp name="SystemSampler.arguments" elementType="Arguments" guiclass="ArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true">
+            <collectionProp name="Arguments.arguments">
+              <elementProp name="" elementType="Argument">
+                <stringProp name="Argument.name"></stringProp>
+                <stringProp name="Argument.value">show</stringProp>
+                <stringProp name="Argument.metadata">=</stringProp>
+              </elementProp>
+              <elementProp name="" elementType="Argument">
+                <stringProp name="Argument.name"></stringProp>
+                <stringProp name="Argument.value">streams</stringProp>
+                <stringProp name="Argument.metadata">=</stringProp>
+              </elementProp>
+            </collectionProp>
+          </elementProp>
+          <elementProp name="SystemSampler.environment" elementType="Arguments" guiclass="ArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true">
+            <collectionProp name="Arguments.arguments"/>
+          </elementProp>
+          <stringProp name="SystemSampler.directory">${k_home}</stringProp>
+        </SystemSampler>
+        <hashTree>
+          <ResponseAssertion guiclass="AssertionGui" testclass="ResponseAssertion" testname="Response Assertion" enabled="true">
+            <collectionProp name="Asserion.test_strings">
+              <stringProp name="3079651">demo</stringProp>
+            </collectionProp>
+            <stringProp name="Assertion.custom_message"></stringProp>
+            <stringProp name="Assertion.test_field">Assertion.response_data</stringProp>
+            <boolProp name="Assertion.assume_success">false</boolProp>
+            <intProp name="Assertion.test_type">16</intProp>
+          </ResponseAssertion>
+          <hashTree/>
+        </hashTree>
+        <SystemSampler guiclass="SystemSamplerGui" testclass="SystemSampler" testname="Describe_Stream" enabled="true">
+          <boolProp name="SystemSampler.checkReturnCode">false</boolProp>
+          <stringProp name="SystemSampler.expectedReturnCode">0</stringProp>
+          <stringProp name="SystemSampler.command">bin/cli</stringProp>
+          <elementProp name="SystemSampler.arguments" elementType="Arguments" guiclass="ArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true">
+            <collectionProp name="Arguments.arguments">
+              <elementProp name="" elementType="Argument">
+                <stringProp name="Argument.name"></stringProp>
+                <stringProp name="Argument.value">describe</stringProp>
+                <stringProp name="Argument.metadata">=</stringProp>
+              </elementProp>
+              <elementProp name="" elementType="Argument">
+                <stringProp name="Argument.name"></stringProp>
+                <stringProp name="Argument.value">stream</stringProp>
+                <stringProp name="Argument.metadata">=</stringProp>
+              </elementProp>
+              <elementProp name="" elementType="Argument">
+                <stringProp name="Argument.name"></stringProp>
+                <stringProp name="Argument.value">demo</stringProp>
+                <stringProp name="Argument.metadata">=</stringProp>
+              </elementProp>
+            </collectionProp>
+          </elementProp>
+          <elementProp name="SystemSampler.environment" elementType="Arguments" guiclass="ArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true">
+            <collectionProp name="Arguments.arguments"/>
+          </elementProp>
+          <stringProp name="SystemSampler.directory">${k_home}</stringProp>
+        </SystemSampler>
+        <hashTree>
+          <ResponseAssertion guiclass="AssertionGui" testclass="ResponseAssertion" testname="Response Assertion" enabled="true">
+            <collectionProp name="Asserion.test_strings">
+              <stringProp name="-757609694">devices/+/messages</stringProp>
+            </collectionProp>
+            <stringProp name="Assertion.custom_message"></stringProp>
+            <stringProp name="Assertion.test_field">Assertion.response_data</stringProp>
+            <boolProp name="Assertion.assume_success">false</boolProp>
+            <intProp name="Assertion.test_type">16</intProp>
+          </ResponseAssertion>
+          <hashTree/>
+        </hashTree>
+        <SystemSampler guiclass="SystemSamplerGui" testclass="SystemSampler" testname="Drop_Stream" enabled="true">
+          <boolProp name="SystemSampler.checkReturnCode">false</boolProp>
+          <stringProp name="SystemSampler.expectedReturnCode">0</stringProp>
+          <stringProp name="SystemSampler.command">bin/cli</stringProp>
+          <elementProp name="SystemSampler.arguments" elementType="Arguments" guiclass="ArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true">
+            <collectionProp name="Arguments.arguments">
+              <elementProp name="" elementType="Argument">
+                <stringProp name="Argument.name"></stringProp>
+                <stringProp name="Argument.value">drop</stringProp>
+                <stringProp name="Argument.metadata">=</stringProp>
+              </elementProp>
+              <elementProp name="" elementType="Argument">
+                <stringProp name="Argument.name"></stringProp>
+                <stringProp name="Argument.value">stream</stringProp>
+                <stringProp name="Argument.metadata">=</stringProp>
+              </elementProp>
+              <elementProp name="" elementType="Argument">
+                <stringProp name="Argument.name"></stringProp>
+                <stringProp name="Argument.value">demo</stringProp>
+                <stringProp name="Argument.metadata">=</stringProp>
+              </elementProp>
+            </collectionProp>
+          </elementProp>
+          <elementProp name="SystemSampler.environment" elementType="Arguments" guiclass="ArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true">
+            <collectionProp name="Arguments.arguments"/>
+          </elementProp>
+          <stringProp name="SystemSampler.directory">${k_home}</stringProp>
+        </SystemSampler>
+        <hashTree>
+          <ResponseAssertion guiclass="AssertionGui" testclass="ResponseAssertion" testname="Response Assertion" enabled="true">
+            <collectionProp name="Asserion.test_strings">
+              <stringProp name="287881319">Stream demo is dropped.</stringProp>
+            </collectionProp>
+            <stringProp name="Assertion.custom_message"></stringProp>
+            <stringProp name="Assertion.test_field">Assertion.response_data</stringProp>
+            <boolProp name="Assertion.assume_success">false</boolProp>
+            <intProp name="Assertion.test_type">16</intProp>
+          </ResponseAssertion>
+          <hashTree/>
+        </hashTree>
+        <SystemSampler guiclass="SystemSamplerGui" testclass="SystemSampler" testname="Get_Empty_Streams" enabled="true">
+          <boolProp name="SystemSampler.checkReturnCode">false</boolProp>
+          <stringProp name="SystemSampler.expectedReturnCode">0</stringProp>
+          <stringProp name="SystemSampler.command">bin/cli</stringProp>
+          <elementProp name="SystemSampler.arguments" elementType="Arguments" guiclass="ArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true">
+            <collectionProp name="Arguments.arguments">
+              <elementProp name="" elementType="Argument">
+                <stringProp name="Argument.name"></stringProp>
+                <stringProp name="Argument.value">show</stringProp>
+                <stringProp name="Argument.metadata">=</stringProp>
+              </elementProp>
+              <elementProp name="" elementType="Argument">
+                <stringProp name="Argument.name"></stringProp>
+                <stringProp name="Argument.value">streams</stringProp>
+                <stringProp name="Argument.metadata">=</stringProp>
+              </elementProp>
+            </collectionProp>
+          </elementProp>
+          <elementProp name="SystemSampler.environment" elementType="Arguments" guiclass="ArgumentsPanel" testclass="Arguments" testname="User Defined Variables" enabled="true">
+            <collectionProp name="Arguments.arguments"/>
+          </elementProp>
+          <stringProp name="SystemSampler.directory">${k_home}</stringProp>
+        </SystemSampler>
+        <hashTree>
+          <ResponseAssertion guiclass="AssertionGui" testclass="ResponseAssertion" testname="Response Assertion" enabled="true">
+            <collectionProp name="Asserion.test_strings">
+              <stringProp name="-222768811">No stream definitions are found</stringProp>
+            </collectionProp>
+            <stringProp name="Assertion.custom_message"></stringProp>
+            <stringProp name="Assertion.test_field">Assertion.response_data</stringProp>
+            <boolProp name="Assertion.assume_success">false</boolProp>
+            <intProp name="Assertion.test_type">16</intProp>
+          </ResponseAssertion>
+          <hashTree/>
+        </hashTree>
+        <ResultCollector guiclass="ViewResultsFullVisualizer" testclass="ResultCollector" testname="View Results Tree" enabled="true">
+          <boolProp name="ResultCollector.error_logging">false</boolProp>
+          <objProp>
+            <name>saveConfig</name>
+            <value class="SampleSaveConfiguration">
+              <time>true</time>
+              <latency>true</latency>
+              <timestamp>true</timestamp>
+              <success>true</success>
+              <label>true</label>
+              <code>true</code>
+              <message>true</message>
+              <threadName>true</threadName>
+              <dataType>true</dataType>
+              <encoding>false</encoding>
+              <assertions>true</assertions>
+              <subresults>true</subresults>
+              <responseData>false</responseData>
+              <samplerData>false</samplerData>
+              <xml>false</xml>
+              <fieldNames>true</fieldNames>
+              <responseHeaders>false</responseHeaders>
+              <requestHeaders>false</requestHeaders>
+              <responseDataOnError>false</responseDataOnError>
+              <saveAssertionResultsFailureMessage>true</saveAssertionResultsFailureMessage>
+              <assertionsResultsToSave>0</assertionsResultsToSave>
+              <bytes>true</bytes>
+              <sentBytes>true</sentBytes>
+              <threadCounts>true</threadCounts>
+              <idleTime>true</idleTime>
+              <connectTime>true</connectTime>
+            </value>
+          </objProp>
+          <stringProp name="filename"></stringProp>
+        </ResultCollector>
+        <hashTree/>
+        <ConstantTimer guiclass="ConstantTimerGui" testclass="ConstantTimer" testname="Constant Timer" enabled="true">
+          <stringProp name="ConstantTimer.delay">500</stringProp>
+        </ConstantTimer>
+        <hashTree/>
+      </hashTree>
+    </hashTree>
+  </hashTree>
+</jmeterTestPlan>

+ 2 - 1
go.mod

@@ -1,12 +1,13 @@
 module github.com/emqx/kuiper
 
 require (
-	github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96 // indirect
+	github.com/benbjohnson/clock v1.0.0
 	github.com/buger/jsonparser v0.0.0-20191004114745-ee4c978eae7e
 	github.com/eclipse/paho.mqtt.golang v1.2.0
 	github.com/go-yaml/yaml v2.1.0+incompatible
 	github.com/golang-collections/collections v0.0.0-20130729185459-604e922904d3
 	github.com/google/uuid v1.1.1
+	github.com/gorilla/mux v1.7.3
 	github.com/patrickmn/go-cache v2.1.0+incompatible
 	github.com/pebbe/zmq4 v1.0.0
 	github.com/prometheus/client_golang v1.2.1

+ 3 - 3
plugins/functions/countPlusOne.go

@@ -5,8 +5,8 @@ import "fmt"
 type countPlusOneFunc struct {
 }
 
-func (f *countPlusOneFunc) Validate(args []interface{}) error{
-	if len(args) != 1{
+func (f *countPlusOneFunc) Validate(args []interface{}) error {
+	if len(args) != 1 {
 		return fmt.Errorf("countPlusOne function only supports 1 parameter but got %d", len(args))
 	}
 	return nil
@@ -14,7 +14,7 @@ func (f *countPlusOneFunc) Validate(args []interface{}) error{
 
 func (f *countPlusOneFunc) Exec(args []interface{}) (interface{}, bool) {
 	arg, ok := args[0].([]interface{})
-	if !ok{
+	if !ok {
 		return fmt.Errorf("arg is not a slice, got %v", args[0]), false
 	}
 	return len(arg) + 1, true

+ 2 - 2
plugins/functions/echo.go

@@ -7,8 +7,8 @@ import (
 type echo struct {
 }
 
-func (f *echo) Validate(args []interface{}) error{
-	if len(args) != 1{
+func (f *echo) Validate(args []interface{}) error {
+	if len(args) != 1 {
 		return fmt.Errorf("echo function only supports 1 parameter but got %d", len(args))
 	}
 	return nil

+ 13 - 13
plugins/sinks/file.go

@@ -12,12 +12,12 @@ import (
 
 type fileSink struct {
 	interval int
-	path string
+	path     string
 
-	results  [][]byte
-	file *os.File
-	mux sync.Mutex
-	cancel context.CancelFunc
+	results [][]byte
+	file    *os.File
+	mux     sync.Mutex
+	cancel  context.CancelFunc
 }
 
 func (m *fileSink) Configure(props map[string]interface{}) error {
@@ -46,18 +46,18 @@ func (m *fileSink) Open(ctx api.StreamContext) error {
 		_, err = os.Create(m.path)
 	}
 	f, err = os.OpenFile(m.path, os.O_APPEND|os.O_WRONLY, os.ModeAppend)
-	if err != nil{
+	if err != nil {
 		return fmt.Errorf("fail to open file sink for %v", err)
 	}
 	m.file = f
 	t := time.NewTicker(time.Duration(m.interval) * time.Millisecond)
 	exeCtx, cancel := ctx.WithCancel()
 	m.cancel = cancel
-	go func(){
+	go func() {
 		defer t.Stop()
-		for{
-			select{
-			case <- t.C:
+		for {
+			select {
+			case <-t.C:
 				m.save(logger)
 			case <-exeCtx.Done():
 				logger.Info("file sink done")
@@ -98,7 +98,7 @@ func (m *fileSink) Collect(ctx api.StreamContext, item interface{}) error {
 		m.mux.Lock()
 		m.results = append(m.results, v)
 		m.mux.Unlock()
-	}else{
+	} else {
 		logger.Debug("file sink receive non byte data")
 	}
 	return nil
@@ -108,11 +108,11 @@ func (m *fileSink) Close(ctx api.StreamContext) error {
 	if m.cancel != nil {
 		m.cancel()
 	}
-	if m.file != nil{
+	if m.file != nil {
 		m.save(ctx.GetLogger())
 		return m.file.Close()
 	}
 	return nil
 }
 
-var File fileSink
+var File fileSink

+ 3 - 4
plugins/sinks/memory.go

@@ -3,7 +3,7 @@ package main
 import "github.com/emqx/kuiper/xstream/api"
 
 type memory struct {
-	results  [][]byte
+	results [][]byte
 }
 
 func (m *memory) Open(ctx api.StreamContext) error {
@@ -18,7 +18,7 @@ func (m *memory) Collect(ctx api.StreamContext, item interface{}) error {
 	if v, ok := item.([]byte); ok {
 		logger.Debugf("memory sink receive %s", item)
 		m.results = append(m.results, v)
-	}else{
+	} else {
 		logger.Debug("memory sink receive non byte data")
 	}
 	return nil
@@ -29,9 +29,8 @@ func (m *memory) Close(ctx api.StreamContext) error {
 	return nil
 }
 
-
 func (m *memory) Configure(props map[string]interface{}) error {
 	return nil
 }
 
-var Memory memory
+var Memory memory

+ 12 - 13
plugins/sinks/zmq.go

@@ -8,11 +8,10 @@ import (
 
 type zmqSink struct {
 	publisher *zmq.Socket
-	srv string
-	topic string
+	srv       string
+	topic     string
 }
 
-
 func (m *zmqSink) Configure(props map[string]interface{}) error {
 	srv, ok := props["server"]
 	if !ok {
@@ -22,10 +21,10 @@ func (m *zmqSink) Configure(props map[string]interface{}) error {
 	if !ok {
 		return fmt.Errorf("zmq source property server %v is not a string", srv)
 	}
-	if tpc, ok := props["topic"]; ok{
-		if t, ok := tpc.(string); !ok{
+	if tpc, ok := props["topic"]; ok {
+		if t, ok := tpc.(string); !ok {
 			return fmt.Errorf("zmq source property topic %v is not a string", tpc)
-		}else{
+		} else {
 			m.topic = t
 		}
 	}
@@ -40,11 +39,11 @@ func (m *zmqSink) Configure(props map[string]interface{}) error {
 func (m *zmqSink) Open(ctx api.StreamContext) (err error) {
 	logger := ctx.GetLogger()
 	m.publisher, err = zmq.NewSocket(zmq.PUB)
-	if err != nil{
+	if err != nil {
 		return fmt.Errorf("zmq sink fails to create socket: %v", err)
 	}
 	err = m.publisher.Bind(m.srv)
-	if err != nil{
+	if err != nil {
 		return fmt.Errorf("zmq sink fails to bind to %s: %v", m.srv, err)
 	}
 	logger.Debugf("zmq sink open")
@@ -55,26 +54,26 @@ func (m *zmqSink) Collect(ctx api.StreamContext, item interface{}) (err error) {
 	logger := ctx.GetLogger()
 	if v, ok := item.([]byte); ok {
 		logger.Debugf("zmq sink receive %s", item)
-		if m.topic == ""{
+		if m.topic == "" {
 			_, err = m.publisher.Send(string(v), 0)
-		}else{
+		} else {
 			msgs := []string{
 				m.topic,
 				string(v),
 			}
 			_, err = m.publisher.SendMessage(msgs)
 		}
-	}else{
+	} else {
 		logger.Debug("zmq sink receive non byte data %v", item)
 	}
-	if err != nil{
+	if err != nil {
 		logger.Debugf("send to zmq error %v", err)
 	}
 	return
 }
 
 func (m *zmqSink) Close(ctx api.StreamContext) error {
-	if m.publisher != nil{
+	if m.publisher != nil {
 		return m.publisher.Close()
 	}
 	return nil

+ 9 - 11
plugins/sources/random.go

@@ -35,21 +35,19 @@ func (s *randomSource) Configure(topic string, props map[string]interface{}) err
 	return nil
 }
 
-func (s *randomSource) Open(ctx api.StreamContext, consume api.ConsumeFunc, onError api.ErrorFunc) {
+func (s *randomSource) Open(ctx api.StreamContext, consumer chan<- api.SourceTuple, errCh chan<- error) {
 	t := time.NewTicker(time.Duration(s.interval) * time.Millisecond)
 	exeCtx, cancel := ctx.WithCancel()
 	s.cancel = cancel
-	go func(exeCtx api.StreamContext) {
-		defer t.Stop()
-		for {
-			select {
-			case <-t.C:
-				consume(randomize(s.pattern, s.seed), nil)
-			case <-exeCtx.Done():
-				return
-			}
+	defer t.Stop()
+	for {
+		select {
+		case <-t.C:
+			consumer <- api.NewDefaultSourceTuple(randomize(s.pattern, s.seed), nil)
+		case <-exeCtx.Done():
+			return
 		}
-	}(exeCtx)
+	}
 }
 
 func randomize(p map[string]interface{}, seed int) map[string]interface{} {

+ 36 - 38
plugins/sources/zmq.go

@@ -25,60 +25,58 @@ func (s *zmqSource) Configure(topic string, props map[string]interface{}) error
 	return nil
 }
 
-func (s *zmqSource) Open(ctx api.StreamContext, consume api.ConsumeFunc, onError api.ErrorFunc) {
+func (s *zmqSource) Open(ctx api.StreamContext, consumer chan<- api.SourceTuple, errCh chan<- error) {
 	logger := ctx.GetLogger()
 	var err error
 	s.subscriber, err = zmq.NewSocket(zmq.SUB)
 	if err != nil {
-		onError(fmt.Errorf("zmq source fails to create socket: %v", err))
+		errCh <- fmt.Errorf("zmq source fails to create socket: %v", err)
 	}
 	err = s.subscriber.Connect(s.srv)
 	if err != nil {
-		onError(fmt.Errorf("zmq source fails to connect to %s: %v", s.srv, err))
+		errCh <- fmt.Errorf("zmq source fails to connect to %s: %v", s.srv, err)
 	}
 	s.subscriber.SetSubscribe(s.topic)
 	logger.Debugf("zmq source subscribe to topic %s", s.topic)
 	exeCtx, cancel := ctx.WithCancel()
 	s.cancel = cancel
-	go func(exeCtx api.StreamContext) {
-		logger.Debugf("start to listen")
-		for {
-			msgs, err := s.subscriber.RecvMessage(0)
-			if err != nil {
-				id, err := s.subscriber.GetIdentity()
-				onError(fmt.Errorf("zmq source getting message %s error: %v", id, err))
-			} else {
-				logger.Debugf("zmq source receive %v", msgs)
-				var m string
-				for i, msg := range msgs {
-					if i == 0 && s.topic != "" {
-						continue
-					}
-					m += msg
-				}
-				meta := make(map[string]interface{})
-				if s.topic != "" {
-					meta["topic"] = msgs[0]
-				}
-				result := make(map[string]interface{})
-				if e := json.Unmarshal([]byte(m), &result); e != nil {
-					logger.Warnf("zmq source message %s is not json", m)
-				} else {
-					consume(result, meta)
+	logger.Debugf("start to listen")
+	for {
+		msgs, err := s.subscriber.RecvMessage(0)
+		if err != nil {
+			id, err := s.subscriber.GetIdentity()
+			errCh <- fmt.Errorf("zmq source getting message %s error: %v", id, err)
+		} else {
+			logger.Debugf("zmq source receive %v", msgs)
+			var m string
+			for i, msg := range msgs {
+				if i == 0 && s.topic != "" {
+					continue
 				}
+				m += msg
 			}
-			select {
-			case <-exeCtx.Done():
-				logger.Infof("zmq source done")
-				if s.subscriber != nil {
-					s.subscriber.Close()
-				}
-				return
-			default:
-				//do nothing
+			meta := make(map[string]interface{})
+			if s.topic != "" {
+				meta["topic"] = msgs[0]
 			}
+			result := make(map[string]interface{})
+			if e := json.Unmarshal([]byte(m), &result); e != nil {
+				logger.Warnf("zmq source message %s is not json", m)
+			} else {
+				consumer <- api.NewDefaultSourceTuple(result, meta)
+			}
+		}
+		select {
+		case <-exeCtx.Done():
+			logger.Infof("zmq source done")
+			if s.subscriber != nil {
+				s.subscriber.Close()
+			}
+			return
+		default:
+			//do nothing
 		}
-	}(exeCtx)
+	}
 }
 
 func (s *zmqSource) Close(ctx api.StreamContext) error {

+ 101 - 104
xsql/ast.go

@@ -11,8 +11,6 @@ import (
 	"time"
 )
 
-
-
 type Node interface {
 	node()
 }
@@ -35,18 +33,18 @@ type Source interface {
 
 type Sources []Source
 
-func (ss Sources) node(){}
+func (ss Sources) node() {}
 
 type Table struct {
-	Name string
+	Name  string
 	Alias string
 }
 
 func (t *Table) source() {}
-func (ss *Table) node(){}
-
+func (ss *Table) node()  {}
 
 type JoinType int
+
 const (
 	LEFT_JOIN JoinType = iota
 	INNER_JOIN
@@ -63,12 +61,13 @@ type Join struct {
 }
 
 func (j *Join) source() {}
-func (ss *Join) node(){}
+func (ss *Join) node()  {}
 
 type Joins []Join
-func (ss Joins) node(){}
 
-type Statement interface{
+func (ss Joins) node() {}
+
+type Statement interface {
 	Stmt()
 	Node
 }
@@ -79,12 +78,12 @@ type SelectStatement struct {
 	Joins      Joins
 	Condition  Expr
 	Dimensions Dimensions
-	Having	   Expr
+	Having     Expr
 	SortFields SortFields
 }
 
 func (ss *SelectStatement) Stmt() {}
-func (ss *SelectStatement) node(){}
+func (ss *SelectStatement) node() {}
 
 type Literal interface {
 	Expr
@@ -105,7 +104,7 @@ type BracketExpr struct {
 
 type ColonExpr struct {
 	Start int
-	End int
+	End   int
 }
 
 type IndexExpr struct {
@@ -141,7 +140,7 @@ type Dimension struct {
 }
 
 type SortField struct {
-	Name string
+	Name      string
 	Ascending bool
 }
 
@@ -150,62 +149,62 @@ type SortFields []SortField
 type Dimensions []Dimension
 
 func (f *Field) expr() {}
-func (f *Field) node(){}
+func (f *Field) node() {}
 
 func (pe *ParenExpr) expr() {}
-func (pe *ParenExpr) node(){}
+func (pe *ParenExpr) node() {}
 
 func (ae *ArrowExpr) expr() {}
-func (ae *ArrowExpr) node(){}
+func (ae *ArrowExpr) node() {}
 
 func (be *BracketExpr) expr() {}
-func (be *BracketExpr) node(){}
+func (be *BracketExpr) node() {}
 
 func (be *ColonExpr) expr() {}
-func (be *ColonExpr) node(){}
+func (be *ColonExpr) node() {}
 
 func (be *IndexExpr) expr() {}
-func (be *IndexExpr) node(){}
+func (be *IndexExpr) node() {}
 
 func (w *Wildcard) expr() {}
-func (w *Wildcard) node(){}
+func (w *Wildcard) node() {}
 
 func (bl *BooleanLiteral) expr()    {}
 func (bl *BooleanLiteral) literal() {}
-func (bl *BooleanLiteral) node(){}
+func (bl *BooleanLiteral) node()    {}
 
 func (tl *TimeLiteral) expr()    {}
 func (tl *TimeLiteral) literal() {}
-func (tl *TimeLiteral) node(){}
+func (tl *TimeLiteral) node()    {}
 
 func (il *IntegerLiteral) expr()    {}
 func (il *IntegerLiteral) literal() {}
-func (il *IntegerLiteral) node(){}
+func (il *IntegerLiteral) node()    {}
 
 func (nl *NumberLiteral) expr()    {}
 func (nl *NumberLiteral) literal() {}
-func (nl *NumberLiteral) node(){}
+func (nl *NumberLiteral) node()    {}
 
 func (sl *StringLiteral) expr()    {}
 func (sl *StringLiteral) literal() {}
-func (sl *StringLiteral) node(){}
+func (sl *StringLiteral) node()    {}
 
 func (d *Dimension) expr() {}
-func (d *Dimension) node(){}
+func (d *Dimension) node() {}
 
-func (d Dimensions) node(){}
-func (d *Dimensions) GetWindow() *Window{
+func (d Dimensions) node() {}
+func (d *Dimensions) GetWindow() *Window {
 	for _, child := range *d {
-		if w, ok := child.Expr.(*Window); ok{
+		if w, ok := child.Expr.(*Window); ok {
 			return w
 		}
 	}
 	return nil
 }
-func (d *Dimensions) GetGroups() Dimensions{
+func (d *Dimensions) GetGroups() Dimensions {
 	var nd Dimensions
 	for _, child := range *d {
-		if _, ok := child.Expr.(*Window); !ok{
+		if _, ok := child.Expr.(*Window); !ok {
 			nd = append(nd, child)
 		}
 	}
@@ -213,18 +212,18 @@ func (d *Dimensions) GetGroups() Dimensions{
 }
 
 func (sf *SortField) expr() {}
-func (sf *SortField) node(){}
+func (sf *SortField) node() {}
 
-func (sf SortFields) node(){}
+func (sf SortFields) node() {}
 
 type Call struct {
 	Name string
 	Args []Expr
 }
 
-func (c *Call) expr() {}
+func (c *Call) expr()    {}
 func (c *Call) literal() {}
-func (c *Call) node(){}
+func (c *Call) node()    {}
 
 type WindowType int
 
@@ -238,7 +237,7 @@ const (
 
 type Window struct {
 	WindowType WindowType
-	Length	   *IntegerLiteral
+	Length     *IntegerLiteral
 	Interval   *IntegerLiteral
 }
 
@@ -246,48 +245,49 @@ func (w *Window) expr()    {}
 func (w *Window) literal() {}
 func (w *Window) node()    {}
 
-type  SelectStatements []SelectStatement
+type SelectStatements []SelectStatement
 
-func (ss *SelectStatements) node(){}
+func (ss *SelectStatements) node() {}
 
 type Fields []Field
-func (fs Fields) node(){}
+
+func (fs Fields) node() {}
 
 type BinaryExpr struct {
-	OP Token
+	OP  Token
 	LHS Expr
 	RHS Expr
 }
 
 func (fe *BinaryExpr) expr() {}
-func (be *BinaryExpr) node(){}
+func (be *BinaryExpr) node() {}
 
 type FieldRef struct {
 	StreamName StreamName
-	Name  string
+	Name       string
 }
 
 func (fr *FieldRef) expr() {}
-func (fr *FieldRef) node(){}
-
+func (fr *FieldRef) node() {}
 
 // The stream AST tree
 type Options map[string]string
+
 func (o Options) node() {}
 
 type StreamName string
+
 func (sn *StreamName) node() {}
 
 type StreamStmt struct {
-	Name StreamName
+	Name         StreamName
 	StreamFields StreamFields
-	Options Options
+	Options      Options
 }
 
-func (ss *StreamStmt) node(){}
+func (ss *StreamStmt) node() {}
 func (ss *StreamStmt) Stmt() {}
 
-
 type FieldType interface {
 	fieldType()
 	Node
@@ -300,29 +300,31 @@ type StreamField struct {
 
 type StreamFields []StreamField
 
-func (sf StreamFields) node(){}
+func (sf StreamFields) node() {}
 
 type BasicType struct {
 	Type DataType
 }
+
 func (bt *BasicType) fieldType() {}
-func (bt *BasicType) node(){}
+func (bt *BasicType) node()      {}
 
 type ArrayType struct {
 	Type DataType
 	FieldType
 }
+
 func (at *ArrayType) fieldType() {}
-func (at *ArrayType) node(){}
+func (at *ArrayType) node()      {}
 
 type RecType struct {
 	StreamFields StreamFields
 }
+
 func (rt *RecType) fieldType() {}
-func (rt *RecType) node(){}
+func (rt *RecType) node()      {}
 
 type ShowStreamsStatement struct {
-
 }
 
 type DescribeStreamStatement struct {
@@ -338,17 +340,16 @@ type DropStreamStatement struct {
 }
 
 func (ss *ShowStreamsStatement) Stmt() {}
-func (ss *ShowStreamsStatement) node(){}
+func (ss *ShowStreamsStatement) node() {}
 
 func (dss *DescribeStreamStatement) Stmt() {}
-func (dss *DescribeStreamStatement) node(){}
+func (dss *DescribeStreamStatement) node() {}
 
 func (ess *ExplainStreamStatement) Stmt() {}
-func (ess *ExplainStreamStatement) node(){}
+func (ess *ExplainStreamStatement) node() {}
 
 func (dss *DropStreamStatement) Stmt() {}
-func (dss *DropStreamStatement) node(){}
-
+func (dss *DropStreamStatement) node() {}
 
 type Visitor interface {
 	Visit(Node) Visitor
@@ -442,7 +443,6 @@ func Walk(v Visitor, node Node) {
 	}
 }
 
-
 // WalkFunc traverses a node hierarchy in depth-first order.
 func WalkFunc(node Node, fn func(Node)) {
 	Walk(walkFuncVisitor(fn), node)
@@ -452,14 +452,12 @@ type walkFuncVisitor func(Node)
 
 func (fn walkFuncVisitor) Visit(n Node) Visitor { fn(n); return fn }
 
-
 // Valuer is the interface that wraps the Value() method.
 type Valuer interface {
 	// Value returns the value and existence flag for a given key.
 	Value(key string) (interface{}, bool)
 }
 
-
 // CallValuer implements the Call method for evaluating function calls.
 type CallValuer interface {
 	Valuer
@@ -489,13 +487,13 @@ type WildcardValuer struct {
 
 //TODO deal with wildcard of a stream, e.g. SELECT Table.* from Table inner join Table1
 func (wv *WildcardValuer) Value(key string) (interface{}, bool) {
-	if key == ""{
+	if key == "" {
 		return wv.Data.All(key)
-	}else{
+	} else {
 		a := strings.Index(key, ".*")
-		if a <= 0{
+		if a <= 0 {
 			return nil, false
-		}else{
+		} else {
 			return wv.Data.All(key[:a])
 		}
 	}
@@ -578,14 +576,13 @@ func (t *Tuple) GetMetadata() Metadata {
 	return t.Metadata
 }
 
-
 func (t *Tuple) IsWatermark() bool {
 	return false
 }
 
 type WindowTuples struct {
 	Emitter string
-	Tuples []Tuple
+	Tuples  []Tuple
 }
 
 type WindowTuplesSet []WindowTuples
@@ -600,26 +597,26 @@ func (w WindowTuplesSet) GetBySrc(src string) []Tuple {
 }
 
 func (w WindowTuplesSet) Len() int {
-	if len(w) > 0{
+	if len(w) > 0 {
 		return len(w[0].Tuples)
 	}
 	return 0
 }
 func (w WindowTuplesSet) Swap(i, j int) {
-	if len(w) > 0{
+	if len(w) > 0 {
 		s := w[0].Tuples
 		s[i], s[j] = s[j], s[i]
 	}
 }
 func (w WindowTuplesSet) Index(i int) Valuer {
-	if len(w) > 0{
+	if len(w) > 0 {
 		s := w[0].Tuples
 		return &(s[i])
 	}
 	return nil
 }
 
-func (w WindowTuplesSet) AddTuple(tuple *Tuple) WindowTuplesSet{
+func (w WindowTuplesSet) AddTuple(tuple *Tuple) WindowTuplesSet {
 	found := false
 	for i, t := range w {
 		if t.Emitter == tuple.Emitter {
@@ -680,15 +677,15 @@ func (jt *JoinTuple) Value(key string) (interface{}, bool) {
 	switch len(keys) {
 	case 1:
 		if len(tuples) > 1 {
-			for _, tuple := range tuples {	//TODO support key without modifier?
+			for _, tuple := range tuples { //TODO support key without modifier?
 				v, ok := tuple.Message[key]
-				if ok{
+				if ok {
 					return v, ok
 				}
 			}
 			common.Log.Infoln("Wrong key: ", key, ", not found")
 			return nil, false
-		} else{
+		} else {
 			v, ok := tuples[0].Message[key]
 			return v, ok
 		}
@@ -709,17 +706,17 @@ func (jt *JoinTuple) Value(key string) (interface{}, bool) {
 }
 
 func (jt *JoinTuple) All(stream string) (interface{}, bool) {
-	if stream != ""{
-		for _, t := range jt.Tuples{
-			if t.Emitter == stream{
+	if stream != "" {
+		for _, t := range jt.Tuples {
+			if t.Emitter == stream {
 				return t.Message, true
 			}
 		}
-	}else{
+	} else {
 		var r Message = make(map[string]interface{})
-		for _, t := range jt.Tuples{
-			for k, v := range t.Message{
-				if _, ok := r[k]; !ok{
+		for _, t := range jt.Tuples {
+			for k, v := range t.Message {
+				if _, ok := r[k]; !ok {
 					r[k] = v
 				}
 			}
@@ -730,8 +727,9 @@ func (jt *JoinTuple) All(stream string) (interface{}, bool) {
 }
 
 type JoinTupleSets []JoinTuple
-func (s JoinTupleSets) Len() int      { return len(s) }
-func (s JoinTupleSets) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
+
+func (s JoinTupleSets) Len() int           { return len(s) }
+func (s JoinTupleSets) Swap(i, j int)      { s[i], s[j] = s[j], s[i] }
 func (s JoinTupleSets) Index(i int) Valuer { return &(s[i]) }
 
 func (s JoinTupleSets) AggregateEval(expr Expr) []interface{} {
@@ -743,6 +741,7 @@ func (s JoinTupleSets) AggregateEval(expr Expr) []interface{} {
 }
 
 type GroupedTuples []DataValuer
+
 func (s GroupedTuples) AggregateEval(expr Expr) []interface{} {
 	var result []interface{}
 	for _, t := range s {
@@ -752,6 +751,7 @@ func (s GroupedTuples) AggregateEval(expr Expr) []interface{} {
 }
 
 type GroupedTuplesSet []GroupedTuples
+
 func (s GroupedTuplesSet) Len() int           { return len(s) }
 func (s GroupedTuplesSet) Swap(i, j int)      { s[i], s[j] = s[j], s[i] }
 func (s GroupedTuplesSet) Index(i int) Valuer { return s[i][0] }
@@ -863,14 +863,14 @@ func (a multiValuer) Call(name string, args []interface{}) (interface{}, bool) {
 	return nil, false
 }
 
-type multiAggregateValuer struct{
-	data AggregateData
+type multiAggregateValuer struct {
+	data    AggregateData
 	valuers []Valuer
 }
 
 func MultiAggregateValuer(data AggregateData, valuers ...Valuer) Valuer {
 	return &multiAggregateValuer{
-		data: data,
+		data:    data,
 		valuers: valuers,
 	}
 }
@@ -892,12 +892,12 @@ func (a *multiAggregateValuer) Call(name string, args []interface{}) (interface{
 			if v, ok := a.Call(name, args); ok {
 				return v, true
 			}
-		}else if c, ok := valuer.(CallValuer); ok{
-			if singleArgs == nil{
-				for _, arg := range args{
-					if arg, ok := arg.([]interface{}); ok{
+		} else if c, ok := valuer.(CallValuer); ok {
+			if singleArgs == nil {
+				for _, arg := range args {
+					if arg, ok := arg.([]interface{}); ok {
 						singleArgs = append(singleArgs, arg[0])
-					}else{
+					} else {
 						common.Log.Infof("multiAggregateValuer does not get [][] args but get %v", args)
 						return nil, false
 					}
@@ -944,20 +944,20 @@ func (v *ValuerEval) Eval(expr Expr) interface{} {
 	case *BooleanLiteral:
 		return expr.Val
 	case *ColonExpr:
-		return &BracketEvalResult{Start:expr.Start, End:expr.End}
+		return &BracketEvalResult{Start: expr.Start, End: expr.End}
 	case *IndexExpr:
-		return &BracketEvalResult{Start:expr.Index, End:expr.Index}
+		return &BracketEvalResult{Start: expr.Index, End: expr.Index}
 	case *Call:
 		if valuer, ok := v.Valuer.(CallValuer); ok {
 			var args []interface{}
 
 			if len(expr.Args) > 0 {
 				args = make([]interface{}, len(expr.Args))
-				if aggreValuer, ok := valuer.(AggregateCallValuer); ok{
+				if aggreValuer, ok := valuer.(AggregateCallValuer); ok {
 					for i := range expr.Args {
 						args[i] = aggreValuer.GetAllTuples().AggregateEval(expr.Args[i])
 					}
-				}else{
+				} else {
 					for i := range expr.Args {
 						args[i] = v.Eval(expr.Args[i])
 					}
@@ -984,7 +984,6 @@ func (v *ValuerEval) Eval(expr Expr) interface{} {
 	}
 }
 
-
 func (v *ValuerEval) evalBinaryExpr(expr *BinaryExpr) interface{} {
 	lhs := v.Eval(expr.LHS)
 	switch val := lhs.(type) {
@@ -1010,8 +1009,7 @@ func (v *ValuerEval) evalBinaryExpr(expr *BinaryExpr) interface{} {
 	return v.simpleDataEval(lhs, rhs, expr.OP)
 }
 
-
-func (v *ValuerEval) evalJsonExpr(result interface{}, op Token,  expr Expr) interface{} {
+func (v *ValuerEval) evalJsonExpr(result interface{}, op Token, expr Expr) interface{} {
 	if val, ok := result.(map[string]interface{}); ok {
 		switch op {
 		case ARROW:
@@ -1049,7 +1047,7 @@ func (v *ValuerEval) evalJsonExpr(result interface{}, op Token,  expr Expr) inte
 						fmt.Printf("End value is out of index: %d of %d.\n", berVal.End, len(val))
 						return nil
 					}
-					return val[berVal.Start : berVal.End]
+					return val[berVal.Start:berVal.End]
 				}
 			} else {
 				fmt.Printf("Invalid evaluation result - %v.\n", berVal)
@@ -1426,7 +1424,7 @@ func (v *ValuerEval) simpleDataEval(lhs, rhs interface{}, op Token) interface{}
 		}
 	case time.Time:
 		rt, err := common.InterfaceToTime(rhs, "")
-		if err != nil{
+		if err != nil {
 			return false
 		}
 		switch op {
@@ -1556,7 +1554,7 @@ func isAggFunc(f *Call) bool {
 	return false
 }
 func HasAggFuncs(node Node) bool {
-	if node == nil{
+	if node == nil {
 		return false
 	}
 	var r = false
@@ -1567,12 +1565,12 @@ func HasAggFuncs(node Node) bool {
 				return
 			}
 		}
-	});
+	})
 	return r
 }
 
 func HasNoAggFuncs(node Node) bool {
-	if node == nil{
+	if node == nil {
 		return false
 	}
 	var r = false
@@ -1586,4 +1584,3 @@ func HasNoAggFuncs(node Node) bool {
 	})
 	return r
 }
-

+ 14 - 14
xsql/ast_agg_stmt_test.go

@@ -9,23 +9,23 @@ import (
 
 func TestIsAggStatement(t *testing.T) {
 	var tests = []struct {
-		s    string
-		agg  bool
-		err  string
+		s   string
+		agg bool
+		err string
 	}{
-		{s: `SELECT avg(1) FROM tbl`,agg: true},
-		{s: `SELECT sin(1) FROM tbl`,agg: false},
-		{s: `SELECT sin(avg(f1)) FROM tbl`,agg: true},
+		{s: `SELECT avg(1) FROM tbl`, agg: true},
+		{s: `SELECT sin(1) FROM tbl`, agg: false},
+		{s: `SELECT sin(avg(f1)) FROM tbl`, agg: true},
 
-		{s: `SELECT sum(f1) FROM tbl GROUP by f1`,agg: true},
-		{s: `SELECT f1 FROM tbl GROUP by f1`,agg: true},
+		{s: `SELECT sum(f1) FROM tbl GROUP by f1`, agg: true},
+		{s: `SELECT f1 FROM tbl GROUP by f1`, agg: true},
 
-		{s: `SELECT count(f1) FROM tbl`,agg: true},
-		{s: `SELECT max(f1) FROM tbl`,agg: true},
-		{s: `SELECT min(f1) FROM tbl`,agg: true},
-		{s: `SELECT count(f1) FROM tbl group by tumblingwindow(ss, 5)`,agg: true},
+		{s: `SELECT count(f1) FROM tbl`, agg: true},
+		{s: `SELECT max(f1) FROM tbl`, agg: true},
+		{s: `SELECT min(f1) FROM tbl`, agg: true},
+		{s: `SELECT count(f1) FROM tbl group by tumblingwindow(ss, 5)`, agg: true},
 
-		{s: `SELECT f1 FROM tbl left join tbl2 on tbl1.f1 = tbl2.f2`,agg: false},
+		{s: `SELECT f1 FROM tbl left join tbl2 on tbl1.f1 = tbl2.f2`, agg: false},
 	}
 
 	fmt.Printf("The test bucket size is %d.\n\n", len(tests))
@@ -39,4 +39,4 @@ func TestIsAggStatement(t *testing.T) {
 			t.Errorf("Error: expected %t, actual %t.", tt.agg, isAgg)
 		}
 	}
-}
+}

+ 1 - 1
xsql/expression_evaluator.go

@@ -1,8 +1,8 @@
 package xsql
 
 import (
-	"github.com/emqx/kuiper/common"
 	"github.com/buger/jsonparser"
+	"github.com/emqx/kuiper/common"
 	"github.com/golang-collections/collections/stack"
 )
 

+ 33 - 33
xsql/funcs_aggregate.go

@@ -8,7 +8,7 @@ import (
 	"strings"
 )
 
-type AggregateFunctionValuer struct{
+type AggregateFunctionValuer struct {
 	Data AggregateData
 }
 
@@ -21,15 +21,15 @@ func (v AggregateFunctionValuer) Call(name string, args []interface{}) (interfac
 	switch lowerName {
 	case "avg":
 		arg0 := args[0].([]interface{})
-		if len(arg0) > 0{
+		if len(arg0) > 0 {
 			v := getFirstValidArg(arg0)
-			switch v.(type){
+			switch v.(type) {
 			case int:
-				return sliceIntTotal(arg0)/len(arg0), true
+				return sliceIntTotal(arg0) / len(arg0), true
 			case int64:
-				return sliceIntTotal(arg0)/len(arg0), true
+				return sliceIntTotal(arg0) / len(arg0), true
 			case float64:
-				return sliceFloatTotal(arg0)/float64(len(arg0)), true
+				return sliceFloatTotal(arg0) / float64(len(arg0)), true
 			default:
 				return fmt.Errorf("invalid data type for avg function"), false
 			}
@@ -40,9 +40,9 @@ func (v AggregateFunctionValuer) Call(name string, args []interface{}) (interfac
 		return len(arg0), true
 	case "max":
 		arg0 := args[0].([]interface{})
-		if len(arg0) > 0{
+		if len(arg0) > 0 {
 			v := getFirstValidArg(arg0)
-			switch t := v.(type){
+			switch t := v.(type) {
 			case int:
 				return sliceIntMax(arg0, t), true
 			case int64:
@@ -58,9 +58,9 @@ func (v AggregateFunctionValuer) Call(name string, args []interface{}) (interfac
 		return fmt.Errorf("empty data for max function"), false
 	case "min":
 		arg0 := args[0].([]interface{})
-		if len(arg0) > 0{
+		if len(arg0) > 0 {
 			v := getFirstValidArg(arg0)
-			switch t := v.(type){
+			switch t := v.(type) {
 			case int:
 				return sliceIntMin(arg0, t), true
 			case int64:
@@ -76,9 +76,9 @@ func (v AggregateFunctionValuer) Call(name string, args []interface{}) (interfac
 		return fmt.Errorf("empty data for max function"), false
 	case "sum":
 		arg0 := args[0].([]interface{})
-		if len(arg0) > 0{
+		if len(arg0) > 0 {
 			v := getFirstValidArg(arg0)
-			switch v.(type){
+			switch v.(type) {
 			case int:
 				return sliceIntTotal(arg0), true
 			case int64:
@@ -94,12 +94,12 @@ func (v AggregateFunctionValuer) Call(name string, args []interface{}) (interfac
 		common.Log.Debugf("run aggregate func %s", name)
 		if nf, err := plugin_manager.GetPlugin(name, "functions"); err != nil {
 			return nil, false
-		}else{
+		} else {
 			f, ok := nf.(api.Function)
 			if !ok {
 				return nil, false
 			}
-			if !f.IsAggregate(){
+			if !f.IsAggregate() {
 				return nil, false
 			}
 			result, ok := f.Exec(args)
@@ -113,18 +113,18 @@ func (v *AggregateFunctionValuer) GetAllTuples() AggregateData {
 	return v.Data
 }
 
-func getFirstValidArg(s []interface{}) interface{}{
-	for _, v := range s{
-		if v != nil{
+func getFirstValidArg(s []interface{}) interface{} {
+	for _, v := range s {
+		if v != nil {
 			return v
 		}
 	}
 	return nil
 }
 
-func sliceIntTotal(s []interface{}) int{
+func sliceIntTotal(s []interface{}) int {
 	var total int
-	for _, v := range s{
+	for _, v := range s {
 		if v, ok := v.(int); ok {
 			total += v
 		}
@@ -132,17 +132,17 @@ func sliceIntTotal(s []interface{}) int{
 	return total
 }
 
-func sliceFloatTotal(s []interface{}) float64{
+func sliceFloatTotal(s []interface{}) float64 {
 	var total float64
-	for _, v := range s{
+	for _, v := range s {
 		if v, ok := v.(float64); ok {
 			total += v
 		}
 	}
 	return total
 }
-func sliceIntMax(s []interface{}, max int) int{
-	for _, v := range s{
+func sliceIntMax(s []interface{}, max int) int {
+	for _, v := range s {
 		if v, ok := v.(int); ok {
 			if max < v {
 				max = v
@@ -151,8 +151,8 @@ func sliceIntMax(s []interface{}, max int) int{
 	}
 	return max
 }
-func sliceFloatMax(s []interface{}, max float64) float64{
-	for _, v := range s{
+func sliceFloatMax(s []interface{}, max float64) float64 {
+	for _, v := range s {
 		if v, ok := v.(float64); ok {
 			if max < v {
 				max = v
@@ -162,8 +162,8 @@ func sliceFloatMax(s []interface{}, max float64) float64{
 	return max
 }
 
-func sliceStringMax(s []interface{}, max string) string{
-	for _, v := range s{
+func sliceStringMax(s []interface{}, max string) string {
+	for _, v := range s {
 		if v, ok := v.(string); ok {
 			if max < v {
 				max = v
@@ -172,8 +172,8 @@ func sliceStringMax(s []interface{}, max string) string{
 	}
 	return max
 }
-func sliceIntMin(s []interface{}, min int) int{
-	for _, v := range s{
+func sliceIntMin(s []interface{}, min int) int {
+	for _, v := range s {
 		if v, ok := v.(int); ok {
 			if min > v {
 				min = v
@@ -182,8 +182,8 @@ func sliceIntMin(s []interface{}, min int) int{
 	}
 	return min
 }
-func sliceFloatMin(s []interface{}, min float64) float64{
-	for _, v := range s{
+func sliceFloatMin(s []interface{}, min float64) float64 {
+	for _, v := range s {
 		if v, ok := v.(float64); ok {
 			if min > v {
 				min = v
@@ -193,8 +193,8 @@ func sliceFloatMin(s []interface{}, min float64) float64{
 	return min
 }
 
-func sliceStringMin(s []interface{}, min string) string{
-	for _, v := range s{
+func sliceStringMin(s []interface{}, min string) string {
+	for _, v := range s {
 		if v, ok := v.(string); ok {
 			if min < v {
 				min = v

+ 28 - 29
xsql/funcs_ast_validator.go

@@ -1,9 +1,9 @@
 package xsql
 
 import (
+	"fmt"
 	"github.com/emqx/kuiper/common/plugin_manager"
 	"github.com/emqx/kuiper/xstream/api"
-	"fmt"
 	"strings"
 )
 
@@ -28,13 +28,13 @@ func validateFuncs(funcName string, args []Expr) error {
 	} else {
 		if nf, err := plugin_manager.GetPlugin(funcName, "functions"); err != nil {
 			return err
-		}else{
+		} else {
 			f, ok := nf.(api.Function)
 			if !ok {
 				return fmt.Errorf("exported symbol %s is not type of api.Function", funcName)
 			}
 			var targs []interface{}
-			for _, arg := range args{
+			for _, arg := range args {
 				targs = append(targs, arg)
 			}
 			return f.Validate(targs)
@@ -46,18 +46,18 @@ func validateMathFunc(name string, args []Expr) error {
 	len := len(args)
 	switch name {
 	case "abs", "acos", "asin", "atan", "ceil", "cos", "cosh", "exp", "ln", "log", "round", "sign", "sin", "sinh",
-	"sqrt", "tan", "tanh" :
+		"sqrt", "tan", "tanh":
 		if err := validateLen(name, 1, len); err != nil {
-			return  err
+			return err
 		}
 		if isStringArg(args[0]) || isTimeArg(args[0]) || isBooleanArg(args[0]) {
 			return produceErrInfo(name, 0, "number - float or int")
 		}
 	case "bitand", "bitor", "bitxor":
 		if err := validateLen(name, 2, len); err != nil {
-			return  err
+			return err
 		}
-		if isFloatArg(args[0]) || isStringArg(args[0]) || isTimeArg(args[0]) || isBooleanArg(args[0]){
+		if isFloatArg(args[0]) || isStringArg(args[0]) || isTimeArg(args[0]) || isBooleanArg(args[0]) {
 			return produceErrInfo(name, 0, "int")
 		}
 		if isFloatArg(args[1]) || isStringArg(args[1]) || isTimeArg(args[1]) || isBooleanArg(args[1]) {
@@ -66,26 +66,26 @@ func validateMathFunc(name string, args []Expr) error {
 
 	case "bitnot":
 		if err := validateLen(name, 1, len); err != nil {
-			return  err
+			return err
 		}
-		if isFloatArg(args[0]) || isStringArg(args[0]) || isTimeArg(args[0]) || isBooleanArg(args[0])  {
+		if isFloatArg(args[0]) || isStringArg(args[0]) || isTimeArg(args[0]) || isBooleanArg(args[0]) {
 			return produceErrInfo(name, 0, "int")
 		}
 
 	case "atan2", "mod", "power":
 		if err := validateLen(name, 2, len); err != nil {
-			return  err
+			return err
 		}
 		if isStringArg(args[0]) || isTimeArg(args[0]) || isBooleanArg(args[0]) {
 			return produceErrInfo(name, 0, "number - float or int")
 		}
-		if isStringArg(args[1]) || isTimeArg(args[1]) || isBooleanArg(args[1]){
+		if isStringArg(args[1]) || isTimeArg(args[1]) || isBooleanArg(args[1]) {
 			return produceErrInfo(name, 1, "number - float or int")
 		}
 
 	case "rand":
 		if err := validateLen(name, 0, len); err != nil {
-			return  err
+			return err
 		}
 	}
 	return nil
@@ -105,44 +105,44 @@ func validateStrFunc(name string, args []Expr) error {
 		}
 	case "endswith", "indexof", "regexp_matches", "startswith":
 		if err := validateLen(name, 2, len); err != nil {
-			return  err
+			return err
 		}
 		for i := 0; i < 2; i++ {
-			if isNumericArg(args[i]) || isTimeArg(args[i])|| isBooleanArg(args[i]) {
+			if isNumericArg(args[i]) || isTimeArg(args[i]) || isBooleanArg(args[i]) {
 				return produceErrInfo(name, i, "string")
 			}
 		}
 	case "format_time":
 		if err := validateLen(name, 2, len); err != nil {
-			return  err
+			return err
 		}
 
-		if isNumericArg(args[0]) || isStringArg(args[0])|| isBooleanArg(args[0]) {
+		if isNumericArg(args[0]) || isStringArg(args[0]) || isBooleanArg(args[0]) {
 			return produceErrInfo(name, 0, "datetime")
 		}
-		if isNumericArg(args[1]) || isTimeArg(args[1])|| isBooleanArg(args[1]) {
+		if isNumericArg(args[1]) || isTimeArg(args[1]) || isBooleanArg(args[1]) {
 			return produceErrInfo(name, 1, "string")
 		}
 
 	case "regexp_replace":
 		if err := validateLen(name, 3, len); err != nil {
-			return  err
+			return err
 		}
 		for i := 0; i < 3; i++ {
-			if isNumericArg(args[i]) || isTimeArg(args[i])|| isBooleanArg(args[i]) {
+			if isNumericArg(args[i]) || isTimeArg(args[i]) || isBooleanArg(args[i]) {
 				return produceErrInfo(name, i, "string")
 			}
 		}
 	case "length", "lower", "ltrim", "numbytes", "rtrim", "trim", "upper":
 		if err := validateLen(name, 1, len); err != nil {
-			return  err
+			return err
 		}
 		if isNumericArg(args[0]) || isTimeArg(args[0]) || isBooleanArg(args[0]) {
 			return produceErrInfo(name, 0, "string")
 		}
 	case "lpad", "rpad":
 		if err := validateLen(name, 2, len); err != nil {
-			return  err
+			return err
 		}
 		if isNumericArg(args[0]) || isTimeArg(args[0]) || isBooleanArg(args[0]) {
 			return produceErrInfo(name, 0, "string")
@@ -168,7 +168,7 @@ func validateStrFunc(name string, args []Expr) error {
 			if sv < 0 {
 				return fmt.Errorf("The start index should not be a nagtive integer.")
 			}
-			if len == 3{
+			if len == 3 {
 				if e, ok1 := args[2].(*IntegerLiteral); ok1 {
 					ev := e.Val
 					if ev < sv {
@@ -204,7 +204,7 @@ func validateConvFunc(name string, args []Expr) error {
 	switch name {
 	case "cast":
 		if err := validateLen(name, 2, len); err != nil {
-			return  err
+			return err
 		}
 		a := args[1]
 		if !isStringArg(a) {
@@ -217,14 +217,14 @@ func validateConvFunc(name string, args []Expr) error {
 		}
 	case "chr":
 		if err := validateLen(name, 1, len); err != nil {
-			return  err
+			return err
 		}
 		if isFloatArg(args[0]) || isTimeArg(args[0]) || isBooleanArg(args[0]) {
 			return produceErrInfo(name, 0, "int")
 		}
 	case "encode":
 		if err := validateLen(name, 2, len); err != nil {
-			return  err
+			return err
 		}
 
 		if isNumericArg(args[0]) || isTimeArg(args[0]) || isBooleanArg(args[0]) {
@@ -242,7 +242,7 @@ func validateConvFunc(name string, args []Expr) error {
 		}
 	case "trunc":
 		if err := validateLen(name, 2, len); err != nil {
-			return  err
+			return err
 		}
 
 		if isTimeArg(args[0]) || isBooleanArg(args[0]) || isStringArg(args[0]) {
@@ -287,7 +287,7 @@ func validateOtherFunc(name string, args []Expr) error {
 		}
 	case "newuuid":
 		if err := validateLen(name, 0, len); err != nil {
-			return  err
+			return err
 		}
 	case "mqtt":
 		if err := validateLen(name, 1, len); err != nil {
@@ -310,7 +310,7 @@ func validateAggFunc(name string, args []Expr) error {
 	switch name {
 	case "avg", "max", "min", "sum":
 		if err := validateLen(name, 1, len); err != nil {
-			return  err
+			return err
 		}
 		if isStringArg(args[0]) || isTimeArg(args[0]) || isBooleanArg(args[0]) {
 			return produceErrInfo(name, 0, "number - float or int")
@@ -323,7 +323,6 @@ func validateAggFunc(name string, args []Expr) error {
 	return nil
 }
 
-
 // Index is starting from 0
 func produceErrInfo(name string, index int, expect string) (err error) {
 	index++

+ 101 - 105
xsql/funcs_ast_validator_test.go

@@ -17,406 +17,404 @@ func TestFuncValidator(t *testing.T) {
 		{
 			s: `SELECT abs(1) FROM tbl`,
 			stmt: &SelectStatement{Fields: []Field{{AName: "", Name: "abs", Expr: &Call{Name: "abs", Args: []Expr{&IntegerLiteral{Val: 1}}}}},
-				Sources: []Source{&Table{Name:"tbl"}},
+				Sources: []Source{&Table{Name: "tbl"}},
 			},
 		},
 
 		{
 			s: `SELECT abs(field1) FROM tbl`,
 			stmt: &SelectStatement{Fields: []Field{{AName: "", Name: "abs", Expr: &Call{Name: "abs", Args: []Expr{&FieldRef{Name: "field1"}}}}},
-				Sources: []Source{&Table{Name:"tbl"}},
+				Sources: []Source{&Table{Name: "tbl"}},
 			},
 		},
 
 		{
-			s: `SELECT abs(1,2) FROM tbl`,
+			s:    `SELECT abs(1,2) FROM tbl`,
 			stmt: nil,
-			err: "The arguments for abs should be 1.",
+			err:  "The arguments for abs should be 1.",
 		},
 
 		{
 			s: `SELECT abs(1.1) FROM tbl`,
 			stmt: &SelectStatement{Fields: []Field{{AName: "", Name: "abs", Expr: &Call{Name: "abs", Args: []Expr{&NumberLiteral{Val: 1.1}}}}},
-				Sources: []Source{&Table{Name:"tbl"}},
+				Sources: []Source{&Table{Name: "tbl"}},
 			},
 		},
 
 		{
-			s: `SELECT abs(true) FROM tbl`,
+			s:    `SELECT abs(true) FROM tbl`,
 			stmt: nil,
-			err: "Expect number - float or int type for 1 parameter of function abs.",
+			err:  "Expect number - float or int type for 1 parameter of function abs.",
 		},
 
 		{
-			s: `SELECT abs("test") FROM tbl`,
+			s:    `SELECT abs("test") FROM tbl`,
 			stmt: nil,
-			err: "Expect number - float or int type for 1 parameter of function abs.",
+			err:  "Expect number - float or int type for 1 parameter of function abs.",
 		},
 
 		{
-			s: `SELECT abs(ss) FROM tbl`,
+			s:    `SELECT abs(ss) FROM tbl`,
 			stmt: nil,
-			err: "Expect number - float or int type for 1 parameter of function abs.",
+			err:  "Expect number - float or int type for 1 parameter of function abs.",
 		},
 
-
 		///
 		{
 			s: `SELECT sin(1) FROM tbl`,
 			stmt: &SelectStatement{Fields: []Field{{AName: "", Name: "sin", Expr: &Call{Name: "sin", Args: []Expr{&IntegerLiteral{Val: 1}}}}},
-				Sources: []Source{&Table{Name:"tbl"}},
+				Sources: []Source{&Table{Name: "tbl"}},
 			},
 		},
 
 		{
 			s: `SELECT sin(1.1) FROM tbl`,
 			stmt: &SelectStatement{Fields: []Field{{AName: "", Name: "sin", Expr: &Call{Name: "sin", Args: []Expr{&NumberLiteral{Val: 1.1}}}}},
-				Sources: []Source{&Table{Name:"tbl"}},
+				Sources: []Source{&Table{Name: "tbl"}},
 			},
 		},
 
 		{
-			s: `SELECT sin(true) FROM tbl`,
+			s:    `SELECT sin(true) FROM tbl`,
 			stmt: nil,
-			err: "Expect number - float or int type for 1 parameter of function sin.",
+			err:  "Expect number - float or int type for 1 parameter of function sin.",
 		},
 
 		{
-			s: `SELECT sin("test") FROM tbl`,
+			s:    `SELECT sin("test") FROM tbl`,
 			stmt: nil,
-			err: "Expect number - float or int type for 1 parameter of function sin.",
+			err:  "Expect number - float or int type for 1 parameter of function sin.",
 		},
 
 		{
-			s: `SELECT sin(ss) FROM tbl`,
+			s:    `SELECT sin(ss) FROM tbl`,
 			stmt: nil,
-			err: "Expect number - float or int type for 1 parameter of function sin.",
+			err:  "Expect number - float or int type for 1 parameter of function sin.",
 		},
 		///
 		{
 			s: `SELECT tanh(1) FROM tbl`,
 			stmt: &SelectStatement{Fields: []Field{{AName: "", Name: "tanh", Expr: &Call{Name: "tanh", Args: []Expr{&IntegerLiteral{Val: 1}}}}},
-				Sources: []Source{&Table{Name:"tbl"}},
+				Sources: []Source{&Table{Name: "tbl"}},
 			},
 		},
 
 		{
 			s: `SELECT tanh(1.1) FROM tbl`,
 			stmt: &SelectStatement{Fields: []Field{{AName: "", Name: "tanh", Expr: &Call{Name: "tanh", Args: []Expr{&NumberLiteral{Val: 1.1}}}}},
-				Sources: []Source{&Table{Name:"tbl"}},
+				Sources: []Source{&Table{Name: "tbl"}},
 			},
 		},
 
 		{
-			s: `SELECT tanh(true) FROM tbl`,
+			s:    `SELECT tanh(true) FROM tbl`,
 			stmt: nil,
-			err: "Expect number - float or int type for 1 parameter of function tanh.",
+			err:  "Expect number - float or int type for 1 parameter of function tanh.",
 		},
 
 		{
-			s: `SELECT tanh("test") FROM tbl`,
+			s:    `SELECT tanh("test") FROM tbl`,
 			stmt: nil,
-			err: "Expect number - float or int type for 1 parameter of function tanh.",
+			err:  "Expect number - float or int type for 1 parameter of function tanh.",
 		},
 
 		{
-			s: `SELECT tanh(ss) FROM tbl`,
+			s:    `SELECT tanh(ss) FROM tbl`,
 			stmt: nil,
-			err: "Expect number - float or int type for 1 parameter of function tanh.",
+			err:  "Expect number - float or int type for 1 parameter of function tanh.",
 		},
 
 		///
 		{
 			s: `SELECT bitxor(1, 2) FROM tbl`,
 			stmt: &SelectStatement{Fields: []Field{{AName: "", Name: "bitxor", Expr: &Call{Name: "bitxor", Args: []Expr{&IntegerLiteral{Val: 1}, &IntegerLiteral{Val: 2}}}}},
-				Sources: []Source{&Table{Name:"tbl"}},
+				Sources: []Source{&Table{Name: "tbl"}},
 			},
 		},
 
 		{
-			s: `SELECT bitxor(1.1, 2) FROM tbl`,
+			s:    `SELECT bitxor(1.1, 2) FROM tbl`,
 			stmt: nil,
-			err: "Expect int type for 1 parameter of function bitxor.",
+			err:  "Expect int type for 1 parameter of function bitxor.",
 		},
 
 		{
-			s: `SELECT bitxor(true, 2) FROM tbl`,
+			s:    `SELECT bitxor(true, 2) FROM tbl`,
 			stmt: nil,
-			err: "Expect int type for 1 parameter of function bitxor.",
+			err:  "Expect int type for 1 parameter of function bitxor.",
 		},
 
 		{
-			s: `SELECT bitxor(1, ss) FROM tbl`,
+			s:    `SELECT bitxor(1, ss) FROM tbl`,
 			stmt: nil,
-			err: "Expect int type for 2 parameter of function bitxor.",
+			err:  "Expect int type for 2 parameter of function bitxor.",
 		},
 
 		{
-			s: `SELECT bitxor(1, 2.2) FROM tbl`,
+			s:    `SELECT bitxor(1, 2.2) FROM tbl`,
 			stmt: nil,
-			err: "Expect int type for 2 parameter of function bitxor.",
+			err:  "Expect int type for 2 parameter of function bitxor.",
 		},
 
 		///
 		{
 			s: `SELECT bitnot(1) FROM tbl`,
 			stmt: &SelectStatement{Fields: []Field{{AName: "", Name: "bitnot", Expr: &Call{Name: "bitnot", Args: []Expr{&IntegerLiteral{Val: 1}}}}},
-				Sources: []Source{&Table{Name:"tbl"}},
+				Sources: []Source{&Table{Name: "tbl"}},
 			},
 		},
 
 		{
-			s: `SELECT bitnot(1.1) FROM tbl`,
+			s:    `SELECT bitnot(1.1) FROM tbl`,
 			stmt: nil,
-			err: "Expect int type for 1 parameter of function bitnot.",
+			err:  "Expect int type for 1 parameter of function bitnot.",
 		},
 
 		{
-			s: `SELECT bitnot(true) FROM tbl`,
+			s:    `SELECT bitnot(true) FROM tbl`,
 			stmt: nil,
-			err: "Expect int type for 1 parameter of function bitnot.",
+			err:  "Expect int type for 1 parameter of function bitnot.",
 		},
 
 		///
 		{
 			s: `SELECT mod(1, 2) FROM tbl`,
 			stmt: &SelectStatement{Fields: []Field{{AName: "", Name: "mod", Expr: &Call{Name: "mod", Args: []Expr{&IntegerLiteral{Val: 1}, &IntegerLiteral{Val: 2}}}}},
-				Sources: []Source{&Table{Name:"tbl"}},
+				Sources: []Source{&Table{Name: "tbl"}},
 			},
 		},
 
 		{
-			s: `SELECT mod("1.1", 2) FROM tbl`,
+			s:    `SELECT mod("1.1", 2) FROM tbl`,
 			stmt: nil,
-			err: "Expect number - float or int type for 1 parameter of function mod.",
+			err:  "Expect number - float or int type for 1 parameter of function mod.",
 		},
 
 		{
-			s: `SELECT mod(1.1, true) FROM tbl`,
+			s:    `SELECT mod(1.1, true) FROM tbl`,
 			stmt: nil,
-			err: "Expect number - float or int type for 2 parameter of function mod.",
+			err:  "Expect number - float or int type for 2 parameter of function mod.",
 		},
 
 		{
-			s: `SELECT mod(1, ss) FROM tbl`,
+			s:    `SELECT mod(1, ss) FROM tbl`,
 			stmt: nil,
-			err: "Expect number - float or int type for 2 parameter of function mod.",
+			err:  "Expect number - float or int type for 2 parameter of function mod.",
 		},
 
 		///
 		{
 			s: `SELECT concat(field, "hello") FROM tbl`,
 			stmt: &SelectStatement{Fields: []Field{{AName: "", Name: "concat", Expr: &Call{Name: "concat", Args: []Expr{&FieldRef{Name: "field"}, &StringLiteral{Val: "hello"}}}}},
-				Sources: []Source{&Table{Name:"tbl"}},
+				Sources: []Source{&Table{Name: "tbl"}},
 			},
 		},
 
 		{
-			s: `SELECT concat("1.1", 2) FROM tbl`,
+			s:    `SELECT concat("1.1", 2) FROM tbl`,
 			stmt: nil,
-			err: "Expect string type for 2 parameter of function concat.",
+			err:  "Expect string type for 2 parameter of function concat.",
 		},
 
 		{
-			s: `SELECT concat("1.1", true) FROM tbl`,
+			s:    `SELECT concat("1.1", true) FROM tbl`,
 			stmt: nil,
-			err: "Expect string type for 2 parameter of function concat.",
+			err:  "Expect string type for 2 parameter of function concat.",
 		},
 
 		{
-			s: `SELECT concat("1", ss) FROM tbl`,
+			s:    `SELECT concat("1", ss) FROM tbl`,
 			stmt: nil,
-			err: "Expect string type for 2 parameter of function concat.",
+			err:  "Expect string type for 2 parameter of function concat.",
 		},
 
 		///
 		{
 			s: `SELECT regexp_matches(field, "hello") FROM tbl`,
 			stmt: &SelectStatement{Fields: []Field{{AName: "", Name: "regexp_matches", Expr: &Call{Name: "regexp_matches", Args: []Expr{&FieldRef{Name: "field"}, &StringLiteral{Val: "hello"}}}}},
-				Sources: []Source{&Table{Name:"tbl"}},
+				Sources: []Source{&Table{Name: "tbl"}},
 			},
 		},
 
 		{
-			s: `SELECT regexp_matches(1, "true") FROM tbl`,
+			s:    `SELECT regexp_matches(1, "true") FROM tbl`,
 			stmt: nil,
-			err: "Expect string type for 1 parameter of function regexp_matches.",
+			err:  "Expect string type for 1 parameter of function regexp_matches.",
 		},
 
 		{
-			s: `SELECT regexp_matches("1.1", 2) FROM tbl`,
+			s:    `SELECT regexp_matches("1.1", 2) FROM tbl`,
 			stmt: nil,
-			err: "Expect string type for 2 parameter of function regexp_matches.",
+			err:  "Expect string type for 2 parameter of function regexp_matches.",
 		},
 
 		///
 		{
 			s: `SELECT regexp_replace(field, "hello", "h") FROM tbl`,
 			stmt: &SelectStatement{Fields: []Field{{AName: "", Name: "regexp_replace", Expr: &Call{Name: "regexp_replace", Args: []Expr{&FieldRef{Name: "field"}, &StringLiteral{Val: "hello"}, &StringLiteral{Val: "h"}}}}},
-				Sources: []Source{&Table{Name:"tbl"}},
+				Sources: []Source{&Table{Name: "tbl"}},
 			},
 		},
 
 		{
-			s: `SELECT regexp_replace(field1, "true", true) FROM tbl`,
+			s:    `SELECT regexp_replace(field1, "true", true) FROM tbl`,
 			stmt: nil,
-			err: "Expect string type for 3 parameter of function regexp_replace.",
+			err:  "Expect string type for 3 parameter of function regexp_replace.",
 		},
 
 		///
 		{
 			s: `SELECT trim(field) FROM tbl`,
 			stmt: &SelectStatement{Fields: []Field{{AName: "", Name: "trim", Expr: &Call{Name: "trim", Args: []Expr{&FieldRef{Name: "field"}}}}},
-				Sources: []Source{&Table{Name:"tbl"}},
+				Sources: []Source{&Table{Name: "tbl"}},
 			},
 		},
 
 		{
-			s: `SELECT trim(1) FROM tbl`,
+			s:    `SELECT trim(1) FROM tbl`,
 			stmt: nil,
-			err: "Expect string type for 1 parameter of function trim.",
+			err:  "Expect string type for 1 parameter of function trim.",
 		},
 
 		///
 		{
 			s: `SELECT rpad(field, 3) FROM tbl`,
 			stmt: &SelectStatement{Fields: []Field{{AName: "", Name: "rpad", Expr: &Call{Name: "rpad", Args: []Expr{&FieldRef{Name: "field"}, &IntegerLiteral{Val: 3}}}}},
-				Sources: []Source{&Table{Name:"tbl"}},
+				Sources: []Source{&Table{Name: "tbl"}},
 			},
 		},
 
 		{
-			s: `SELECT rpad("ff", true) FROM tbl`,
+			s:    `SELECT rpad("ff", true) FROM tbl`,
 			stmt: nil,
-			err: "Expect int type for 2 parameter of function rpad.",
+			err:  "Expect int type for 2 parameter of function rpad.",
 		},
 
 		///
 		{
 			s: `SELECT substring(field, 3, 4) FROM tbl`,
 			stmt: &SelectStatement{Fields: []Field{{AName: "", Name: "substring", Expr: &Call{Name: "substring", Args: []Expr{&FieldRef{Name: "field"}, &IntegerLiteral{Val: 3}, &IntegerLiteral{Val: 4}}}}},
-				Sources: []Source{&Table{Name:"tbl"}},
+				Sources: []Source{&Table{Name: "tbl"}},
 			},
 		},
 
 		{
-			s: `SELECT substring(field, -1, 4) FROM tbl`,
+			s:    `SELECT substring(field, -1, 4) FROM tbl`,
 			stmt: nil,
-			err: "The start index should not be a nagtive integer.",
+			err:  "The start index should not be a nagtive integer.",
 		},
 
 		{
-			s: `SELECT substring(field, 0, -1) FROM tbl`,
+			s:    `SELECT substring(field, 0, -1) FROM tbl`,
 			stmt: nil,
-			err: "The end index should be larger than start index.",
+			err:  "The end index should be larger than start index.",
 		},
 
 		{
-			s: `SELECT substring(field, 0, true) FROM tbl`,
+			s:    `SELECT substring(field, 0, true) FROM tbl`,
 			stmt: nil,
-			err: "Expect int type for 3 parameter of function substring.",
+			err:  "Expect int type for 3 parameter of function substring.",
 		},
 
 		///
 		{
 			s: `SELECT cast(field, "bigint") FROM tbl`,
 			stmt: &SelectStatement{Fields: []Field{{AName: "", Name: "cast", Expr: &Call{Name: "cast", Args: []Expr{&FieldRef{Name: "field"}, &StringLiteral{Val: "bigint"}}}}},
-				Sources: []Source{&Table{Name:"tbl"}},
+				Sources: []Source{&Table{Name: "tbl"}},
 			},
 		},
 
 		{
-			s: `SELECT cast("12", "bool") FROM tbl`,
+			s:    `SELECT cast("12", "bool") FROM tbl`,
 			stmt: nil,
-			err: "Expect one of following value for the 2nd parameter: bigint, float, string, boolean, datetime.",
+			err:  "Expect one of following value for the 2nd parameter: bigint, float, string, boolean, datetime.",
 		},
 
 		///
 		{
 			s: `SELECT chr(field) FROM tbl`,
 			stmt: &SelectStatement{Fields: []Field{{AName: "", Name: "chr", Expr: &Call{Name: "chr", Args: []Expr{&FieldRef{Name: "field"}}}}},
-				Sources: []Source{&Table{Name:"tbl"}},
+				Sources: []Source{&Table{Name: "tbl"}},
 			},
 		},
 
 		{
-			s: `SELECT chr(true) FROM tbl`,
+			s:    `SELECT chr(true) FROM tbl`,
 			stmt: nil,
-			err: "Expect int type for 1 parameter of function chr.",
+			err:  "Expect int type for 1 parameter of function chr.",
 		},
 
 		///
 		{
 			s: `SELECT encode(field, "base64") FROM tbl`,
 			stmt: &SelectStatement{Fields: []Field{{AName: "", Name: "encode", Expr: &Call{Name: "encode", Args: []Expr{&FieldRef{Name: "field"}, &StringLiteral{Val: "base64"}}}}},
-				Sources: []Source{&Table{Name:"tbl"}},
+				Sources: []Source{&Table{Name: "tbl"}},
 			},
 		},
 
 		{
-			s: `SELECT encode(field, true) FROM tbl`,
+			s:    `SELECT encode(field, true) FROM tbl`,
 			stmt: nil,
-			err: "Expect string type for 2 parameter of function encode.",
+			err:  "Expect string type for 2 parameter of function encode.",
 		},
 
 		///
 		{
 			s: `SELECT trunc(field, 3) FROM tbl`,
 			stmt: &SelectStatement{Fields: []Field{{AName: "", Name: "trunc", Expr: &Call{Name: "trunc", Args: []Expr{&FieldRef{Name: "field"}, &IntegerLiteral{Val: 3}}}}},
-				Sources: []Source{&Table{Name:"tbl"}},
+				Sources: []Source{&Table{Name: "tbl"}},
 			},
 		},
 
 		{
-			s: `SELECT trunc(5, ss) FROM tbl`,
+			s:    `SELECT trunc(5, ss) FROM tbl`,
 			stmt: nil,
-			err: "Expect int type for 2 parameter of function trunc.",
+			err:  "Expect int type for 2 parameter of function trunc.",
 		},
 
 		///
 		{
 			s: `SELECT sha512(field) FROM tbl`,
 			stmt: &SelectStatement{Fields: []Field{{AName: "", Name: "sha512", Expr: &Call{Name: "sha512", Args: []Expr{&FieldRef{Name: "field"}}}}},
-				Sources: []Source{&Table{Name:"tbl"}},
+				Sources: []Source{&Table{Name: "tbl"}},
 			},
 		},
 
 		{
-			s: `SELECT sha512(20) FROM tbl`,
+			s:    `SELECT sha512(20) FROM tbl`,
 			stmt: nil,
-			err: "Expect string type for 1 parameter of function sha512.",
+			err:  "Expect string type for 1 parameter of function sha512.",
 		},
 
 		{
-			s: `SELECT mqtt("topic") FROM tbl`,
+			s:    `SELECT mqtt("topic") FROM tbl`,
 			stmt: nil,
-			err: "Expect field reference type for 1 parameter of function mqtt.",
+			err:  "Expect field reference type for 1 parameter of function mqtt.",
 		},
 
 		{
-			s: `SELECT mqtt(topic1) FROM tbl`,
+			s:    `SELECT mqtt(topic1) FROM tbl`,
 			stmt: nil,
-			err: "Parameter of mqtt function can be only topic or messageid.",
+			err:  "Parameter of mqtt function can be only topic or messageid.",
 		},
 
 		{
-			s: `SELECT split_value(topic1) FROM tbl`,
+			s:    `SELECT split_value(topic1) FROM tbl`,
 			stmt: nil,
-			err: "the arguments for split_value should be 3",
+			err:  "the arguments for split_value should be 3",
 		},
 
 		{
-			s: `SELECT split_value(topic1, 3, 1) FROM tbl`,
+			s:    `SELECT split_value(topic1, 3, 1) FROM tbl`,
 			stmt: nil,
-			err: "Expect string type for 2 parameter of function split_value.",
+			err:  "Expect string type for 2 parameter of function split_value.",
 		},
 
 		{
-			s: `SELECT split_value(topic1, "hello", -1) FROM tbl`,
+			s:    `SELECT split_value(topic1, "hello", -1) FROM tbl`,
 			stmt: nil,
-			err: "The index should not be a nagtive integer.",
+			err:  "The index should not be a nagtive integer.",
 		},
-
 	}
 
 	fmt.Printf("The test bucket size is %d.\n\n", len(tests))
@@ -430,5 +428,3 @@ func TestFuncValidator(t *testing.T) {
 		}
 	}
 }
-
-

+ 1 - 1
xsql/funcs_math.go

@@ -197,4 +197,4 @@ func toF64(arg interface{}) (float64, error) {
 		return float64(v), nil
 	}
 	return 0, fmt.Errorf("only float64 & int type are supported")
-}
+}

+ 3 - 5
xsql/funcs_misc.go

@@ -6,8 +6,8 @@ import (
 	"crypto/sha256"
 	"crypto/sha512"
 	b64 "encoding/base64"
-	"github.com/emqx/kuiper/common"
 	"fmt"
+	"github.com/emqx/kuiper/common"
 	"github.com/google/uuid"
 	"hash"
 	"io"
@@ -165,7 +165,7 @@ func round(num float64) int {
 
 func toFixed(num float64, precision int) float64 {
 	output := math.Pow(10, float64(precision))
-	return float64(round(num * output)) / output
+	return float64(round(num*output)) / output
 }
 
 func hashCall(name string, args []interface{}) (interface{}, bool) {
@@ -196,7 +196,7 @@ func otherCall(name string, args []interface{}) (interface{}, bool) {
 	case "newuuid":
 		if uuid, err := uuid.NewUUID(); err != nil {
 			return err, false
-		}else{
+		} else {
 			return uuid.String(), true
 		}
 	case "timestamp":
@@ -210,5 +210,3 @@ func otherCall(name string, args []interface{}) (interface{}, bool) {
 		return fmt.Errorf("unknown function name %s", name), false
 	}
 }
-
-

+ 15 - 16
xsql/funcs_str.go

@@ -2,8 +2,8 @@ package xsql
 
 import (
 	"bytes"
-	"github.com/emqx/kuiper/common"
 	"fmt"
+	"github.com/emqx/kuiper/common"
 	"regexp"
 	"strings"
 	"time"
@@ -34,7 +34,7 @@ func strCall(name string, args []interface{}) (interface{}, bool) {
 	case "lpad":
 		arg0 := common.ToString(args[0])
 		arg1, err := common.ToInt(args[1])
-		if err != nil{
+		if err != nil {
 			return err, false
 		}
 		return strings.Repeat(" ", arg1) + arg0, true
@@ -46,38 +46,38 @@ func strCall(name string, args []interface{}) (interface{}, bool) {
 		return len(arg0), true
 	case "format_time":
 		arg0 := args[0]
-		if t, ok := arg0.(time.Time); ok{
+		if t, ok := arg0.(time.Time); ok {
 			arg1 := common.ToString(args[1])
-			if s, err := common.FormatTime(t, arg1); err==nil{
+			if s, err := common.FormatTime(t, arg1); err == nil {
 				return s, true
 			}
 		}
 		return "", false
 	case "regexp_matches":
 		arg0, arg1 := common.ToString(args[0]), common.ToString(args[1])
-		if matched, err := regexp.MatchString(arg1, arg0); err != nil{
+		if matched, err := regexp.MatchString(arg1, arg0); err != nil {
 			return err, false
-		}else{
+		} else {
 			return matched, true
 		}
 	case "regexp_replace":
 		arg0, arg1, arg2 := common.ToString(args[0]), common.ToString(args[1]), common.ToString(args[2])
-		if re, err := regexp.Compile(arg1); err != nil{
+		if re, err := regexp.Compile(arg1); err != nil {
 			return err, false
-		}else{
+		} else {
 			return re.ReplaceAllString(arg0, arg2), true
 		}
 	case "regexp_substr":
 		arg0, arg1 := common.ToString(args[0]), common.ToString(args[1])
-		if re, err := regexp.Compile(arg1); err != nil{
+		if re, err := regexp.Compile(arg1); err != nil {
 			return err, false
-		}else{
+		} else {
 			return re.FindString(arg0), true
 		}
 	case "rpad":
 		arg0 := common.ToString(args[0])
 		arg1, err := common.ToInt(args[1])
-		if err != nil{
+		if err != nil {
 			return err, false
 		}
 		return arg0 + strings.Repeat(" ", arg1), true
@@ -87,16 +87,16 @@ func strCall(name string, args []interface{}) (interface{}, bool) {
 	case "substring":
 		arg0 := common.ToString(args[0])
 		arg1, err := common.ToInt(args[1])
-		if err != nil{
+		if err != nil {
 			return err, false
 		}
-		if len(args) > 2{
+		if len(args) > 2 {
 			arg2, err := common.ToInt(args[2])
-			if err != nil{
+			if err != nil {
 				return err, false
 			}
 			return arg0[arg1:arg2], true
-		}else{
+		} else {
 			return arg0[arg1:], true
 		}
 	case "startswith":
@@ -121,4 +121,3 @@ func strCall(name string, args []interface{}) (interface{}, bool) {
 		return fmt.Errorf("unknown string function name %s", name), false
 	}
 }
-

+ 10 - 10
xsql/functions.go

@@ -15,8 +15,8 @@ func (*FunctionValuer) Value(key string) (interface{}, bool) {
 
 var aggFuncMap = map[string]string{"avg": "",
 	"count": "",
-	"max": "", "min": "",
-	"sum":  "",
+	"max":   "", "min": "",
+	"sum": "",
 }
 
 var mathFuncMap = map[string]string{"abs": "", "acos": "", "asin": "", "atan": "", "atan2": "",
@@ -32,10 +32,10 @@ var mathFuncMap = map[string]string{"abs": "", "acos": "", "asin": "", "atan": "
 }
 
 var strFuncMap = map[string]string{"concat": "",
-	"endswith": "",
+	"endswith":    "",
 	"format_time": "",
-	"indexof":  "",
-	"length":   "", "lower": "", "lpad": "", "ltrim": "",
+	"indexof":     "",
+	"length":      "", "lower": "", "lpad": "", "ltrim": "",
 	"numbytes":       "",
 	"regexp_matches": "", "regexp_replace": "", "regexp_substr": "", "rpad": "", "rtrim": "",
 	"substring": "", "startswith": "", "split_value": "",
@@ -48,7 +48,7 @@ var convFuncMap = map[string]string{"concat": "", "cast": "", "chr": "",
 	"trunc":  "",
 }
 
-var hashFuncMap = map[string]string{ "md5": "",
+var hashFuncMap = map[string]string{"md5": "",
 	"sha1": "", "sha256": "", "sha384": "", "sha512": "",
 }
 
@@ -68,18 +68,18 @@ func (*FunctionValuer) Call(name string, args []interface{}) (interface{}, bool)
 		return hashCall(lowerName, args)
 	} else if _, ok := otherFuncMap[lowerName]; ok {
 		return otherCall(lowerName, args)
-	} else if _, ok :=  aggFuncMap[lowerName]; ok {
+	} else if _, ok := aggFuncMap[lowerName]; ok {
 		return nil, false
 	} else {
 		common.Log.Debugf("run func %s", name)
 		if nf, err := plugin_manager.GetPlugin(name, "functions"); err != nil {
 			return nil, false
-		}else{
+		} else {
 			f, ok := nf.(api.Function)
 			if !ok {
 				return nil, false
 			}
-			if f.IsAggregate(){
+			if f.IsAggregate() {
 				return nil, false
 			}
 			result, ok := f.Exec(args)
@@ -87,4 +87,4 @@ func (*FunctionValuer) Call(name string, args []interface{}) (interface{}, bool)
 			return result, ok
 		}
 	}
-}
+}

+ 23 - 21
xsql/lexical.go

@@ -47,7 +47,7 @@ const (
 	GTE // >=
 
 	SUBSET //[
-	ARROW //->
+	ARROW  //->
 
 	operatorEnd
 
@@ -56,11 +56,11 @@ const (
 	COMMA     // ,
 	LPAREN    // (
 	RPAREN    // )
-	LBRACKET //[
+	LBRACKET  //[
 	RBRACKET  //]
 	HASH      // #
 	DOT       // .
-	COLON	  //:
+	COLON     //:
 	SEMICOLON //;
 
 	// Keywords
@@ -166,7 +166,7 @@ var tokens = []string{
 	WHERE:  "WHERE",
 	GROUP:  "GROUP",
 	ORDER:  "ORDER",
-	HAVING:  "HAVING",
+	HAVING: "HAVING",
 	BY:     "BY",
 	ASC:    "ASC",
 	DESC:   "DESC",
@@ -188,18 +188,18 @@ var tokens = []string{
 	XARRAY:    "ARRAY",
 	XSTRUCT:   "STRUCT",
 
-	DATASOURCE:   "DATASOURCE",
-	KEY:      "KEY",
-	FORMAT:   "FORMAT",
-	CONF_KEY: "CONF_KEY",
-	TYPE: 	  "TYPE",
+	DATASOURCE:        "DATASOURCE",
+	KEY:               "KEY",
+	FORMAT:            "FORMAT",
+	CONF_KEY:          "CONF_KEY",
+	TYPE:              "TYPE",
 	STRICT_VALIDATION: "STRICT_VALIDATION",
-	TIMESTAMP: "TIMESTAMP",
-	TIMESTAMP_FORMAT: "TIMESTAMP_FORMAT",
+	TIMESTAMP:         "TIMESTAMP",
+	TIMESTAMP_FORMAT:  "TIMESTAMP_FORMAT",
 
-	AND: "AND",
-	OR:  "OR",
-	TRUE: "TRUE",
+	AND:   "AND",
+	OR:    "OR",
+	TRUE:  "TRUE",
 	FALSE: "FALSE",
 
 	DD: "DD",
@@ -591,7 +591,9 @@ func isDigit(ch rune) bool { return ch >= '0' && ch <= '9' }
 
 func isQuotation(ch rune) bool { return ch == '"' }
 
-func (tok Token) isOperator() bool { return (tok > operatorBeg && tok < operatorEnd) || tok == ASTERISK || tok == LBRACKET }
+func (tok Token) isOperator() bool {
+	return (tok > operatorBeg && tok < operatorEnd) || tok == ASTERISK || tok == LBRACKET
+}
 
 func (tok Token) isTimeLiteral() bool { return tok >= DD && tok <= MS }
 
@@ -632,13 +634,13 @@ const (
 )
 
 var dataTypes = []string{
-	BIGINT	: "bigint",
-	FLOAT	: "float",
-	STRINGS	: "string",
+	BIGINT:   "bigint",
+	FLOAT:    "float",
+	STRINGS:  "string",
 	DATETIME: "datetime",
-	BOOLEAN	: "boolean",
-	ARRAY	: "array",
-	STRUCT	: "struct",
+	BOOLEAN:  "boolean",
+	ARRAY:    "array",
+	STRUCT:   "struct",
 }
 
 func (d DataType) isSimpleType() bool {

+ 4 - 3
xsql/metadata_util.go

@@ -7,7 +7,8 @@ const INTERNAL_MQTT_MSG_ID_KEY string = "internal_mqtt_msg_id_key_$$"
 
 //For functions such as mqtt(topic). If the field definitions also has a field named "topic", then it need to
 //have an internal key for "topic" to avoid key conflicts.
-var SpecialKeyMapper = map[string]string{"topic" : INTERNAL_MQTT_TOPIC_KEY, "messageid" : INTERNAL_MQTT_MSG_ID_KEY}
+var SpecialKeyMapper = map[string]string{"topic": INTERNAL_MQTT_TOPIC_KEY, "messageid": INTERNAL_MQTT_MSG_ID_KEY}
+
 func AddSpecialKeyMap(left, right string) {
 	SpecialKeyMapper[left] = right
 }
@@ -16,7 +17,7 @@ func AddSpecialKeyMap(left, right string) {
 The function is used for re-write the parameter names.
 For example, for mqtt function, the arguments could be 'topic' or 'messageid'.
 If the field name defined in stream happens to be 'topic' or 'messageid', it will have conflicts.
- */
+*/
 func (c Call) rewrite_func() *Call {
 	if strings.ToLower(c.Name) == "mqtt" {
 		if f, ok := c.Args[0].(*FieldRef); ok {
@@ -27,4 +28,4 @@ func (c Call) rewrite_func() *Call {
 		}
 	}
 	return &c
-}
+}

+ 15 - 17
xsql/parser.go

@@ -270,7 +270,7 @@ func (p *Parser) parseJoins() (Joins, error) {
 }
 
 func (p *Parser) ParseJoin(joinType JoinType) (*Join, error) {
-	var j = &Join{ JoinType : joinType }
+	var j = &Join{JoinType: joinType}
 	if src, alias, err := p.parseSourceLiteral(); err != nil {
 		return nil, err
 	} else {
@@ -319,7 +319,6 @@ func (p *Parser) parseDimensions() (Dimensions, error) {
 	return ds, nil
 }
 
-
 func (p *Parser) parseHaving() (Expr, error) {
 	if tok, _ := p.scanIgnoreWhitespace(); tok != HAVING {
 		p.unscan()
@@ -332,7 +331,6 @@ func (p *Parser) parseHaving() (Expr, error) {
 	return expr, nil
 }
 
-
 func (p *Parser) parseSorts() (SortFields, error) {
 	var ss SortFields
 	if t, _ := p.scanIgnoreWhitespace(); t == ORDER {
@@ -527,20 +525,20 @@ func (p *Parser) parseUnaryExpr() (Expr, error) {
 		if v, err := strconv.ParseBool(lit); err != nil {
 			return nil, fmt.Errorf("found %q, invalid boolean value.", lit)
 		} else {
-			return &BooleanLiteral{Val:v}, nil
+			return &BooleanLiteral{Val: v}, nil
 		}
 	} else if tok.isTimeLiteral() {
-		return &TimeLiteral{Val:tok}, nil
+		return &TimeLiteral{Val: tok}, nil
 	}
 
 	return nil, fmt.Errorf("found %q, expected expression.", lit)
 }
 
-func (p *Parser) parseBracketExpr() (Expr, error){
+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: -1}, nil
 	} else if tok2 == INTEGER {
 		start, err := strconv.Atoi(lit2)
 		if err != nil {
@@ -548,7 +546,7 @@ func (p *Parser) parseBracketExpr() (Expr, error){
 		}
 		if tok3, _ := p.scanIgnoreWhitespace(); tok3 == RBRACKET {
 			//Such as field[2]
-			return &IndexExpr{Index:start}, nil
+			return &IndexExpr{Index: start}, nil
 		} else if tok3 == COLON {
 			//Such as field[2:] or field[2:4]
 			return p.parseColonExpr(start)
@@ -569,12 +567,12 @@ func (p *Parser) parseColonExpr(start int) (Expr, error) {
 		}
 
 		if tok1, lit1 := p.scanIgnoreWhitespace(); tok1 == RBRACKET {
-			return &ColonExpr{Start:start, End: end}, nil
+			return &ColonExpr{Start: start, End: end}, nil
 		} else {
 			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: -1}, nil
 	}
 	return nil, fmt.Errorf("Found %q, expected right bracket.", lit)
 }
@@ -597,7 +595,7 @@ func (p *Parser) parseCall(name string) (Expr, error) {
 			if tok2, lit2 := p.scanIgnoreWhitespace(); tok2 != RPAREN {
 				return nil, fmt.Errorf("found %q, expected right paren.", lit2)
 			} else {
-				args = append(args, &StringLiteral{Val:"*"})
+				args = append(args, &StringLiteral{Val: "*"})
 				return Call{Name: name, Args: args}.rewrite_func(), nil
 			}
 		} else {
@@ -667,7 +665,7 @@ func validateWindow(funcName string, expectLen int, args []Expr) error {
 		return fmt.Errorf("The 1st argument for %s is expecting timer literal expression. One value of [dd|hh|mi|ss|ms].\n", funcName)
 	}
 
-	for i := 1; i< len(args); i++ {
+	for i := 1; i < len(args); i++ {
 		if _, ok := args[i].(*IntegerLiteral); !ok {
 			return fmt.Errorf("The %d argument for %s is expecting interger literal expression. \n", i, funcName)
 		}
@@ -680,7 +678,7 @@ func (p *Parser) ConvertToWindows(wtype WindowType, name string, args []Expr) (*
 	win := &Window{WindowType: wtype}
 	var unit = 1
 	v := args[0].(*TimeLiteral).Val
-	switch v{
+	switch v {
 	case DD:
 		unit = 24 * 3600 * 1000
 	case HH:
@@ -694,10 +692,10 @@ func (p *Parser) ConvertToWindows(wtype WindowType, name string, args []Expr) (*
 	default:
 		return nil, fmt.Errorf("Invalid timeliteral %s", v)
 	}
-	win.Length = &IntegerLiteral{Val :  args[1].(*IntegerLiteral).Val * unit}
-	if len(args) > 2{
-		win.Interval = &IntegerLiteral{Val : args[2].(*IntegerLiteral).Val * unit}
-	}else{
+	win.Length = &IntegerLiteral{Val: args[1].(*IntegerLiteral).Val * unit}
+	if len(args) > 2 {
+		win.Interval = &IntegerLiteral{Val: args[2].(*IntegerLiteral).Val * unit}
+	} else {
 		win.Interval = &IntegerLiteral{Val: 0}
 	}
 	return win, nil

文件差异内容过多而无法显示
+ 337 - 351
xsql/parser_test.go


+ 6 - 7
xsql/plans/aggregate_operator.go

@@ -1,16 +1,15 @@
 package plans
 
 import (
+	"fmt"
 	"github.com/emqx/kuiper/xsql"
 	"github.com/emqx/kuiper/xstream/api"
-	"fmt"
 )
 
 type AggregatePlan struct {
 	Dimensions xsql.Dimensions
 }
 
-
 /**
  *  input: *xsql.Tuple from preprocessor | xsql.WindowTuplesSet from windowOp | xsql.JoinTupleSets from joinOp
  *  output: xsql.GroupedTuplesSet
@@ -51,19 +50,19 @@ func (p *AggregatePlan) Apply(ctx api.StreamContext, data interface{}) interface
 		for _, d := range p.Dimensions {
 			name += fmt.Sprintf("%v,", ve.Eval(d.Expr))
 		}
-		if ts, ok := result[name]; !ok{
+		if ts, ok := result[name]; !ok {
 			result[name] = xsql.GroupedTuples{m}
-		}else{
+		} else {
 			result[name] = append(ts, m)
 		}
 	}
-	if len(result) > 0{
+	if len(result) > 0 {
 		g := make([]xsql.GroupedTuples, 0, len(result))
 		for _, v := range result {
 			g = append(g, v)
 		}
 		return xsql.GroupedTuplesSet(g)
-	}else{
+	} else {
 		return nil
 	}
-}
+}

+ 61 - 62
xsql/plans/aggregate_test.go

@@ -12,8 +12,8 @@ import (
 
 func TestAggregatePlan_Apply(t *testing.T) {
 	var tests = []struct {
-		sql  string
-		data interface{}
+		sql    string
+		data   interface{}
 		result xsql.GroupedTuplesSet
 	}{
 		{
@@ -21,8 +21,8 @@ func TestAggregatePlan_Apply(t *testing.T) {
 			data: &xsql.Tuple{
 				Emitter: "tbl",
 				Message: xsql.Message{
-					"abc" : int64(6),
-					"def" : "hello",
+					"abc": int64(6),
+					"def": "hello",
 				},
 			},
 			result: xsql.GroupedTuplesSet{
@@ -30,30 +30,29 @@ func TestAggregatePlan_Apply(t *testing.T) {
 					&xsql.Tuple{
 						Emitter: "tbl",
 						Message: xsql.Message{
-							"abc" : int64(6),
-							"def" : "hello",
+							"abc": int64(6),
+							"def": "hello",
 						},
 					},
 				},
 			},
 		},
 
-
 		{
 			sql: "SELECT abc FROM src1 GROUP BY TUMBLINGWINDOW(ss, 10), f1",
 			data: xsql.WindowTuplesSet{
 				xsql.WindowTuples{
-					Emitter:"src1",
-					Tuples:[]xsql.Tuple{
+					Emitter: "src1",
+					Tuples: []xsql.Tuple{
 						{
 							Emitter: "src1",
-							Message: xsql.Message{ "id1" : 1, "f1" : "v1", },
-						},{
+							Message: xsql.Message{"id1": 1, "f1": "v1"},
+						}, {
 							Emitter: "src1",
-							Message: xsql.Message{ "id1" : 2, "f1" : "v2", },
-						},{
+							Message: xsql.Message{"id1": 2, "f1": "v2"},
+						}, {
 							Emitter: "src1",
-							Message: xsql.Message{ "id1" : 3, "f1" : "v1", },
+							Message: xsql.Message{"id1": 3, "f1": "v1"},
 						},
 					},
 				},
@@ -62,17 +61,17 @@ func TestAggregatePlan_Apply(t *testing.T) {
 				{
 					&xsql.Tuple{
 						Emitter: "src1",
-						Message: xsql.Message{ "id1" : 1, "f1" : "v1", },
+						Message: xsql.Message{"id1": 1, "f1": "v1"},
 					},
 					&xsql.Tuple{
 						Emitter: "src1",
-						Message: xsql.Message{ "id1" : 3, "f1" : "v1", },
+						Message: xsql.Message{"id1": 3, "f1": "v1"},
 					},
 				},
 				{
 					&xsql.Tuple{
 						Emitter: "src1",
-						Message: xsql.Message{ "id1" : 2, "f1" : "v2", },
+						Message: xsql.Message{"id1": 2, "f1": "v2"},
 					},
 				},
 			},
@@ -81,17 +80,17 @@ func TestAggregatePlan_Apply(t *testing.T) {
 			sql: "SELECT abc FROM src1 GROUP BY id1, TUMBLINGWINDOW(ss, 10), f1",
 			data: xsql.WindowTuplesSet{
 				xsql.WindowTuples{
-					Emitter:"src1",
-					Tuples:[]xsql.Tuple{
+					Emitter: "src1",
+					Tuples: []xsql.Tuple{
 						{
 							Emitter: "src1",
-							Message: xsql.Message{ "id1" : 1, "f1" : "v1", },
-						},{
+							Message: xsql.Message{"id1": 1, "f1": "v1"},
+						}, {
 							Emitter: "src1",
-							Message: xsql.Message{ "id1" : 2, "f1" : "v2", },
-						},{
+							Message: xsql.Message{"id1": 2, "f1": "v2"},
+						}, {
 							Emitter: "src1",
-							Message: xsql.Message{ "id1" : 3, "f1" : "v1", },
+							Message: xsql.Message{"id1": 3, "f1": "v1"},
 						},
 					},
 				},
@@ -100,19 +99,19 @@ func TestAggregatePlan_Apply(t *testing.T) {
 				{
 					&xsql.Tuple{
 						Emitter: "src1",
-						Message: xsql.Message{ "id1" : 1, "f1" : "v1", },
+						Message: xsql.Message{"id1": 1, "f1": "v1"},
 					},
 				},
 				{
 					&xsql.Tuple{
 						Emitter: "src1",
-						Message: xsql.Message{ "id1" : 2, "f1" : "v2", },
+						Message: xsql.Message{"id1": 2, "f1": "v2"},
 					},
 				},
 				{
 					&xsql.Tuple{
 						Emitter: "src1",
-						Message: xsql.Message{ "id1" : 3, "f1" : "v1", },
+						Message: xsql.Message{"id1": 3, "f1": "v1"},
 					},
 				},
 			},
@@ -123,19 +122,19 @@ func TestAggregatePlan_Apply(t *testing.T) {
 			data: xsql.JoinTupleSets{
 				xsql.JoinTuple{
 					Tuples: []xsql.Tuple{
-						{Emitter: "src1", Message: xsql.Message{ "id1" : 1, "f1" : "v1",},},
-						{Emitter: "src2", Message: xsql.Message{ "id2" : 2, "f2" : "w2",},},
+						{Emitter: "src1", Message: xsql.Message{"id1": 1, "f1": "v1"}},
+						{Emitter: "src2", Message: xsql.Message{"id2": 2, "f2": "w2"}},
 					},
 				},
 				xsql.JoinTuple{
 					Tuples: []xsql.Tuple{
-						{Emitter: "src1", Message: xsql.Message{ "id1" : 2, "f1" : "v2",},},
-						{Emitter: "src2", Message: xsql.Message{ "id2" : 4, "f2" : "w3",},},
+						{Emitter: "src1", Message: xsql.Message{"id1": 2, "f1": "v2"}},
+						{Emitter: "src2", Message: xsql.Message{"id2": 4, "f2": "w3"}},
 					},
 				},
 				xsql.JoinTuple{
 					Tuples: []xsql.Tuple{
-						{Emitter: "src1", Message: xsql.Message{ "id1" : 3, "f1" : "v1",},},
+						{Emitter: "src1", Message: xsql.Message{"id1": 3, "f1": "v1"}},
 					},
 				},
 			},
@@ -143,23 +142,23 @@ func TestAggregatePlan_Apply(t *testing.T) {
 				{
 					&xsql.JoinTuple{
 						Tuples: []xsql.Tuple{
-							{Emitter: "src1", Message: xsql.Message{ "id1" : 1, "f1" : "v1",},},
-							{Emitter: "src2", Message: xsql.Message{ "id2" : 2, "f2" : "w2",},},
+							{Emitter: "src1", Message: xsql.Message{"id1": 1, "f1": "v1"}},
+							{Emitter: "src2", Message: xsql.Message{"id2": 2, "f2": "w2"}},
 						},
 					},
 				},
 				{
 					&xsql.JoinTuple{
 						Tuples: []xsql.Tuple{
-							{Emitter: "src1", Message: xsql.Message{ "id1" : 2, "f1" : "v2",},},
-							{Emitter: "src2", Message: xsql.Message{ "id2" : 4, "f2" : "w3",},},
+							{Emitter: "src1", Message: xsql.Message{"id1": 2, "f1": "v2"}},
+							{Emitter: "src2", Message: xsql.Message{"id2": 4, "f2": "w3"}},
 						},
 					},
 				},
 				{
 					&xsql.JoinTuple{
 						Tuples: []xsql.Tuple{
-							{Emitter: "src1", Message: xsql.Message{ "id1" : 3, "f1" : "v1",},},
+							{Emitter: "src1", Message: xsql.Message{"id1": 3, "f1": "v1"}},
 						},
 					},
 				},
@@ -170,19 +169,19 @@ func TestAggregatePlan_Apply(t *testing.T) {
 			data: xsql.JoinTupleSets{
 				xsql.JoinTuple{
 					Tuples: []xsql.Tuple{
-						{Emitter: "src1", Message: xsql.Message{ "id1" : 1, "f1" : "v1",},},
-						{Emitter: "src2", Message: xsql.Message{ "id2" : 2, "f2" : "w2",},},
+						{Emitter: "src1", Message: xsql.Message{"id1": 1, "f1": "v1"}},
+						{Emitter: "src2", Message: xsql.Message{"id2": 2, "f2": "w2"}},
 					},
 				},
 				xsql.JoinTuple{
 					Tuples: []xsql.Tuple{
-						{Emitter: "src1", Message: xsql.Message{ "id1" : 2, "f1" : "v2",},},
-						{Emitter: "src2", Message: xsql.Message{ "id2" : 4, "f2" : "w3",},},
+						{Emitter: "src1", Message: xsql.Message{"id1": 2, "f1": "v2"}},
+						{Emitter: "src2", Message: xsql.Message{"id2": 4, "f2": "w3"}},
 					},
 				},
 				xsql.JoinTuple{
 					Tuples: []xsql.Tuple{
-						{Emitter: "src1", Message: xsql.Message{ "id1" : 3, "f1" : "v1",},},
+						{Emitter: "src1", Message: xsql.Message{"id1": 3, "f1": "v1"}},
 					},
 				},
 			},
@@ -190,21 +189,21 @@ func TestAggregatePlan_Apply(t *testing.T) {
 				{
 					&xsql.JoinTuple{
 						Tuples: []xsql.Tuple{
-							{Emitter: "src1", Message: xsql.Message{ "id1" : 1, "f1" : "v1",},},
-							{Emitter: "src2", Message: xsql.Message{ "id2" : 2, "f2" : "w2",},},
+							{Emitter: "src1", Message: xsql.Message{"id1": 1, "f1": "v1"}},
+							{Emitter: "src2", Message: xsql.Message{"id2": 2, "f2": "w2"}},
 						},
 					},
 					&xsql.JoinTuple{
 						Tuples: []xsql.Tuple{
-							{Emitter: "src1", Message: xsql.Message{ "id1" : 3, "f1" : "v1",},},
+							{Emitter: "src1", Message: xsql.Message{"id1": 3, "f1": "v1"}},
 						},
 					},
 				},
 				{
 					&xsql.JoinTuple{
 						Tuples: []xsql.Tuple{
-							{Emitter: "src1", Message: xsql.Message{ "id1" : 2, "f1" : "v2",},},
-							{Emitter: "src2", Message: xsql.Message{ "id2" : 4, "f2" : "w3",},},
+							{Emitter: "src1", Message: xsql.Message{"id1": 2, "f1": "v2"}},
+							{Emitter: "src2", Message: xsql.Message{"id2": 4, "f2": "w3"}},
 						},
 					},
 				},
@@ -215,19 +214,19 @@ func TestAggregatePlan_Apply(t *testing.T) {
 			data: xsql.JoinTupleSets{
 				xsql.JoinTuple{
 					Tuples: []xsql.Tuple{
-						{Emitter: "src1", Message: xsql.Message{ "id1" : 1, "f1" : "v1", "ts": common.TimeFromUnixMilli(1568854515000),},},
-						{Emitter: "src2", Message: xsql.Message{ "id2" : 2, "f2" : "w2",},},
+						{Emitter: "src1", Message: xsql.Message{"id1": 1, "f1": "v1", "ts": common.TimeFromUnixMilli(1568854515000)}},
+						{Emitter: "src2", Message: xsql.Message{"id2": 2, "f2": "w2"}},
 					},
 				},
 				xsql.JoinTuple{
 					Tuples: []xsql.Tuple{
-						{Emitter: "src1", Message: xsql.Message{ "id1" : 2, "f1" : "v2","ts": common.TimeFromUnixMilli(1568854573431),},},
-						{Emitter: "src2", Message: xsql.Message{ "id2" : 4, "f2" : "w3",},},
+						{Emitter: "src1", Message: xsql.Message{"id1": 2, "f1": "v2", "ts": common.TimeFromUnixMilli(1568854573431)}},
+						{Emitter: "src2", Message: xsql.Message{"id2": 4, "f2": "w3"}},
 					},
 				},
 				xsql.JoinTuple{
 					Tuples: []xsql.Tuple{
-						{Emitter: "src1", Message: xsql.Message{ "id1" : 3, "f1" : "v1","ts": common.TimeFromUnixMilli(1568854515000),},},
+						{Emitter: "src1", Message: xsql.Message{"id1": 3, "f1": "v1", "ts": common.TimeFromUnixMilli(1568854515000)}},
 					},
 				},
 			},
@@ -235,21 +234,21 @@ func TestAggregatePlan_Apply(t *testing.T) {
 				{
 					&xsql.JoinTuple{
 						Tuples: []xsql.Tuple{
-							{Emitter: "src1", Message: xsql.Message{ "id1" : 1, "f1" : "v1", "ts": common.TimeFromUnixMilli(1568854515000),},},
-							{Emitter: "src2", Message: xsql.Message{ "id2" : 2, "f2" : "w2",},},
+							{Emitter: "src1", Message: xsql.Message{"id1": 1, "f1": "v1", "ts": common.TimeFromUnixMilli(1568854515000)}},
+							{Emitter: "src2", Message: xsql.Message{"id2": 2, "f2": "w2"}},
 						},
 					},
 					&xsql.JoinTuple{
 						Tuples: []xsql.Tuple{
-							{Emitter: "src1", Message: xsql.Message{ "id1" : 3, "f1" : "v1", "ts": common.TimeFromUnixMilli(1568854515000),},},
+							{Emitter: "src1", Message: xsql.Message{"id1": 3, "f1": "v1", "ts": common.TimeFromUnixMilli(1568854515000)}},
 						},
 					},
 				},
 				{
 					&xsql.JoinTuple{
 						Tuples: []xsql.Tuple{
-							{Emitter: "src1", Message: xsql.Message{ "id1" : 2, "f1" : "v2","ts": common.TimeFromUnixMilli(1568854573431),},},
-							{Emitter: "src2", Message: xsql.Message{ "id2" : 4, "f2" : "w3",},},
+							{Emitter: "src1", Message: xsql.Message{"id1": 2, "f1": "v2", "ts": common.TimeFromUnixMilli(1568854573431)}},
+							{Emitter: "src2", Message: xsql.Message{"id2": 4, "f2": "w3"}},
 						},
 					},
 				},
@@ -267,7 +266,7 @@ func TestAggregatePlan_Apply(t *testing.T) {
 			break
 		}
 
-		pp := &AggregatePlan{Dimensions:stmt.Dimensions.GetGroups()}
+		pp := &AggregatePlan{Dimensions: stmt.Dimensions.GetGroups()}
 		result := pp.Apply(ctx, tt.data)
 		gr, ok := result.(xsql.GroupedTuplesSet)
 		if !ok {
@@ -277,14 +276,14 @@ func TestAggregatePlan_Apply(t *testing.T) {
 			t.Errorf("%d. %q\n\nresult mismatch:\n\nexp=%#v\n\ngot=%#v\n\n", i, tt.sql, tt.result, gr)
 		}
 
-		for _, r := range tt.result{
+		for _, r := range tt.result {
 			matched := false
-			for _, gre := range gr{
-				if reflect.DeepEqual(r, gre){
+			for _, gre := range gr {
+				if reflect.DeepEqual(r, gre) {
 					matched = true
 				}
 			}
-			if !matched{
+			if !matched {
 				t.Errorf("%d. %q\n\nresult mismatch:\n\nexp=%#v\n\ngot=%#v\n\n", i, tt.sql, tt.result, r)
 			}
 		}

+ 4 - 4
xsql/plans/filter_operator.go

@@ -10,8 +10,8 @@ type FilterPlan struct {
 }
 
 /**
-  *  input: *xsql.Tuple from preprocessor | xsql.WindowTuplesSet from windowOp | xsql.JoinTupleSets from joinOp
-  *  output: *xsql.Tuple | xsql.WindowTuplesSet | xsql.JoinTupleSets
+ *  input: *xsql.Tuple from preprocessor | xsql.WindowTuplesSet from windowOp | xsql.JoinTupleSets from joinOp
+ *  output: *xsql.Tuple | xsql.WindowTuplesSet | xsql.JoinTupleSets
  */
 func (p *FilterPlan) Apply(ctx api.StreamContext, data interface{}) interface{} {
 	log := ctx.GetLogger()
@@ -65,7 +65,7 @@ func (p *FilterPlan) Apply(ctx api.StreamContext, data interface{}) interface{}
 				return nil
 			}
 		}
-		if len(r) > 0{
+		if len(r) > 0 {
 			return r
 		}
 	default:
@@ -73,4 +73,4 @@ func (p *FilterPlan) Apply(ctx api.StreamContext, data interface{}) interface{}
 		return nil
 	}
 	return nil
-}
+}

+ 51 - 52
xsql/plans/filter_test.go

@@ -12,8 +12,8 @@ import (
 
 func TestFilterPlan_Apply(t *testing.T) {
 	var tests = []struct {
-		sql  string
-		data interface{}
+		sql    string
+		data   interface{}
 		result interface{}
 	}{
 		{
@@ -21,7 +21,7 @@ func TestFilterPlan_Apply(t *testing.T) {
 			data: &xsql.Tuple{
 				Emitter: "tbl",
 				Message: xsql.Message{
-					"a" : int64(6),
+					"a": int64(6),
 				},
 			},
 			result: nil,
@@ -32,17 +32,17 @@ func TestFilterPlan_Apply(t *testing.T) {
 			data: &xsql.Tuple{
 				Emitter: "tbl",
 				Message: xsql.Message{
-					"abc" : common.TimeFromUnixMilli(1568854515000),
-					"def" : common.TimeFromUnixMilli(1568853515000),
-					"ghi" : common.TimeFromUnixMilli(1568854515000),
+					"abc": common.TimeFromUnixMilli(1568854515000),
+					"def": common.TimeFromUnixMilli(1568853515000),
+					"ghi": common.TimeFromUnixMilli(1568854515000),
 				},
 			},
 			result: &xsql.Tuple{
 				Emitter: "tbl",
 				Message: xsql.Message{
-					"abc" : common.TimeFromUnixMilli(1568854515000),
-					"def" : common.TimeFromUnixMilli(1568853515000),
-					"ghi" : common.TimeFromUnixMilli(1568854515000),
+					"abc": common.TimeFromUnixMilli(1568854515000),
+					"def": common.TimeFromUnixMilli(1568853515000),
+					"ghi": common.TimeFromUnixMilli(1568854515000),
 				},
 			},
 		},
@@ -52,7 +52,7 @@ func TestFilterPlan_Apply(t *testing.T) {
 			data: &xsql.Tuple{
 				Emitter: "tbl",
 				Message: xsql.Message{
-					"abc" : int64(6),
+					"abc": int64(6),
 				},
 			},
 			result: &xsql.Tuple{
@@ -68,15 +68,15 @@ func TestFilterPlan_Apply(t *testing.T) {
 			data: &xsql.Tuple{
 				Emitter: "tbl",
 				Message: xsql.Message{
-					"abc" : int64(34),
-					"def" : "hello",
+					"abc": int64(34),
+					"def": "hello",
 				},
 			},
 			result: &xsql.Tuple{
 				Emitter: "tbl",
 				Message: xsql.Message{
-					"abc" : int64(34),
-					"def" : "hello",
+					"abc": int64(34),
+					"def": "hello",
 				},
 			},
 		},
@@ -86,15 +86,15 @@ func TestFilterPlan_Apply(t *testing.T) {
 			data: &xsql.Tuple{
 				Emitter: "tbl",
 				Message: xsql.Message{
-					"abc" : common.TimeFromUnixMilli(1568854515678),
-					"def" : "hello",
+					"abc": common.TimeFromUnixMilli(1568854515678),
+					"def": "hello",
 				},
 			},
 			result: &xsql.Tuple{
 				Emitter: "tbl",
 				Message: xsql.Message{
-					"abc" : common.TimeFromUnixMilli(1568854515678),
-					"def" : "hello",
+					"abc": common.TimeFromUnixMilli(1568854515678),
+					"def": "hello",
 				},
 			},
 		},
@@ -103,31 +103,31 @@ func TestFilterPlan_Apply(t *testing.T) {
 			sql: "SELECT abc FROM src1 WHERE f1 = \"v1\" GROUP BY TUMBLINGWINDOW(ss, 10)",
 			data: xsql.WindowTuplesSet{
 				xsql.WindowTuples{
-					Emitter:"src1",
-					Tuples:[]xsql.Tuple{
+					Emitter: "src1",
+					Tuples: []xsql.Tuple{
 						{
 							Emitter: "src1",
-							Message: xsql.Message{"id1" : 1, "f1" : "v1"},
-						},{
+							Message: xsql.Message{"id1": 1, "f1": "v1"},
+						}, {
 							Emitter: "src1",
-							Message: xsql.Message{"id1" : 2, "f1" : "v2"},
-						},{
+							Message: xsql.Message{"id1": 2, "f1": "v2"},
+						}, {
 							Emitter: "src1",
-							Message: xsql.Message{"id1" : 3, "f1" : "v1"},
+							Message: xsql.Message{"id1": 3, "f1": "v1"},
 						},
 					},
 				},
 			},
 			result: xsql.WindowTuplesSet{
 				xsql.WindowTuples{
-					Emitter:"src1",
-					Tuples:[]xsql.Tuple{
+					Emitter: "src1",
+					Tuples: []xsql.Tuple{
 						{
 							Emitter: "src1",
-							Message: xsql.Message{"id1" : 1, "f1" : "v1"},
-						},{
+							Message: xsql.Message{"id1": 1, "f1": "v1"},
+						}, {
 							Emitter: "src1",
-							Message: xsql.Message{"id1" : 3, "f1" : "v1"},
+							Message: xsql.Message{"id1": 3, "f1": "v1"},
 						},
 					},
 				},
@@ -138,17 +138,17 @@ func TestFilterPlan_Apply(t *testing.T) {
 			sql: "SELECT abc FROM src1 WHERE f1 = \"v8\" GROUP BY TUMBLINGWINDOW(ss, 10)",
 			data: xsql.WindowTuplesSet{
 				xsql.WindowTuples{
-					Emitter:"src1",
-					Tuples:[]xsql.Tuple{
+					Emitter: "src1",
+					Tuples: []xsql.Tuple{
 						{
 							Emitter: "src1",
-							Message: xsql.Message{ "id1" : 1, "f1" : "v1"},
-						},{
+							Message: xsql.Message{"id1": 1, "f1": "v1"},
+						}, {
 							Emitter: "src1",
-							Message: xsql.Message{ "id1" : 2, "f1" : "v2"},
-						},{
+							Message: xsql.Message{"id1": 2, "f1": "v2"},
+						}, {
 							Emitter: "src1",
-							Message: xsql.Message{ "id1" : 3, "f1" : "v1"},
+							Message: xsql.Message{"id1": 3, "f1": "v1"},
 						},
 					},
 				},
@@ -161,32 +161,32 @@ func TestFilterPlan_Apply(t *testing.T) {
 			data: xsql.JoinTupleSets{
 				xsql.JoinTuple{
 					Tuples: []xsql.Tuple{
-						{Emitter: "src1", Message: xsql.Message{ "id1" : 1, "f1" : "v1",},},
-						{Emitter: "src2", Message: xsql.Message{ "id2" : 2, "f2" : "w2",},},
+						{Emitter: "src1", Message: xsql.Message{"id1": 1, "f1": "v1"}},
+						{Emitter: "src2", Message: xsql.Message{"id2": 2, "f2": "w2"}},
 					},
 				},
 				xsql.JoinTuple{
 					Tuples: []xsql.Tuple{
-						{Emitter: "src1", Message: xsql.Message{ "id1" : 2, "f1" : "v2",},},
-						{Emitter: "src2", Message: xsql.Message{ "id2" : 4, "f2" : "w3",},},
+						{Emitter: "src1", Message: xsql.Message{"id1": 2, "f1": "v2"}},
+						{Emitter: "src2", Message: xsql.Message{"id2": 4, "f2": "w3"}},
 					},
 				},
 				xsql.JoinTuple{
 					Tuples: []xsql.Tuple{
-						{Emitter: "src1", Message: xsql.Message{ "id1" : 3, "f1" : "v1",},},
+						{Emitter: "src1", Message: xsql.Message{"id1": 3, "f1": "v1"}},
 					},
 				},
 			},
 			result: xsql.JoinTupleSets{
 				xsql.JoinTuple{
 					Tuples: []xsql.Tuple{
-						{Emitter: "src1", Message: xsql.Message{ "id1" : 1, "f1" : "v1",},},
-						{Emitter: "src2", Message: xsql.Message{ "id2" : 2, "f2" : "w2",},},
+						{Emitter: "src1", Message: xsql.Message{"id1": 1, "f1": "v1"}},
+						{Emitter: "src2", Message: xsql.Message{"id2": 2, "f2": "w2"}},
 					},
 				},
 				xsql.JoinTuple{
 					Tuples: []xsql.Tuple{
-						{Emitter: "src1", Message: xsql.Message{ "id1" : 3, "f1" : "v1",},},
+						{Emitter: "src1", Message: xsql.Message{"id1": 3, "f1": "v1"}},
 					},
 				},
 			},
@@ -197,25 +197,24 @@ func TestFilterPlan_Apply(t *testing.T) {
 			data: xsql.JoinTupleSets{
 				xsql.JoinTuple{
 					Tuples: []xsql.Tuple{
-						{Emitter: "src1", Message: xsql.Message{ "id1" : 1, "f1" : "v1",},},
-						{Emitter: "src2", Message: xsql.Message{ "id2" : 2, "f2" : "w2",},},
+						{Emitter: "src1", Message: xsql.Message{"id1": 1, "f1": "v1"}},
+						{Emitter: "src2", Message: xsql.Message{"id2": 2, "f2": "w2"}},
 					},
 				},
 				xsql.JoinTuple{
 					Tuples: []xsql.Tuple{
-						{Emitter: "src1", Message: xsql.Message{ "id1" : 2, "f1" : "v2",},},
-						{Emitter: "src2", Message: xsql.Message{ "id2" : 4, "f2" : "w3",},},
+						{Emitter: "src1", Message: xsql.Message{"id1": 2, "f1": "v2"}},
+						{Emitter: "src2", Message: xsql.Message{"id2": 4, "f2": "w3"}},
 					},
 				},
 				xsql.JoinTuple{
 					Tuples: []xsql.Tuple{
-						{Emitter: "src1", Message: xsql.Message{ "id1" : 3, "f1" : "v1",},},
+						{Emitter: "src1", Message: xsql.Message{"id1": 3, "f1": "v1"}},
 					},
 				},
 			},
 			result: nil,
 		},
-
 	}
 
 	fmt.Printf("The test bucket size is %d.\n\n", len(tests))
@@ -228,7 +227,7 @@ func TestFilterPlan_Apply(t *testing.T) {
 			break
 		}
 
-		pp := &FilterPlan{Condition:stmt.Condition}
+		pp := &FilterPlan{Condition: stmt.Condition}
 		result := pp.Apply(ctx, tt.data)
 		if !reflect.DeepEqual(tt.result, result) {
 			t.Errorf("%d. %q\n\nresult mismatch:\n\nexp=%#v\n\ngot=%#v\n\n", i, tt.sql, tt.result, result)

+ 1 - 1
xsql/plans/having_operator.go

@@ -71,7 +71,7 @@ func (p *HavingPlan) Apply(ctx api.StreamContext, data interface{}) interface{}
 				return nil
 			}
 		}
-		if len(r) > 0{
+		if len(r) > 0 {
 			return r
 		}
 	default:

+ 35 - 37
xsql/plans/having_test.go

@@ -12,43 +12,42 @@ import (
 
 func TestHavingPlan_Apply(t *testing.T) {
 	var tests = []struct {
-		sql  string
-		data interface{}
+		sql    string
+		data   interface{}
 		result interface{}
 	}{
 		{
 			sql: `SELECT id1 FROM src1 HAVING avg(id1) > 1`,
 			data: xsql.WindowTuplesSet{
 				xsql.WindowTuples{
-					Emitter:"src1",
-					Tuples:[]xsql.Tuple{
+					Emitter: "src1",
+					Tuples: []xsql.Tuple{
 						{
 							Emitter: "src1",
-							Message: xsql.Message{"id1" : 1, "f1" : "v1"},
-						},{
+							Message: xsql.Message{"id1": 1, "f1": "v1"},
+						}, {
 							Emitter: "src1",
-							Message: xsql.Message{"id1" : 2, "f1" : "v2"},
-						},{
+							Message: xsql.Message{"id1": 2, "f1": "v2"},
+						}, {
 							Emitter: "src1",
-							Message: xsql.Message{"id1" : 5, "f1" : "v1"},
+							Message: xsql.Message{"id1": 5, "f1": "v1"},
 						},
-
 					},
 				},
 			},
 			result: xsql.WindowTuplesSet{
 				xsql.WindowTuples{
-					Emitter:"src1",
-					Tuples:[]xsql.Tuple{
+					Emitter: "src1",
+					Tuples: []xsql.Tuple{
 						{
 							Emitter: "src1",
-							Message: xsql.Message{"id1" : 1, "f1" : "v1"},
-						},{
+							Message: xsql.Message{"id1": 1, "f1": "v1"},
+						}, {
 							Emitter: "src1",
-							Message: xsql.Message{"id1" : 2, "f1" : "v2"},
-						},{
+							Message: xsql.Message{"id1": 2, "f1": "v2"},
+						}, {
 							Emitter: "src1",
-							Message: xsql.Message{"id1" : 5, "f1" : "v1"},
+							Message: xsql.Message{"id1": 5, "f1": "v1"},
 						},
 					},
 				},
@@ -59,11 +58,11 @@ func TestHavingPlan_Apply(t *testing.T) {
 			sql: `SELECT id1 FROM src1 HAVING sum(id1) > 1`,
 			data: xsql.WindowTuplesSet{
 				xsql.WindowTuples{
-					Emitter:"src1",
-					Tuples:[]xsql.Tuple{
+					Emitter: "src1",
+					Tuples: []xsql.Tuple{
 						{
 							Emitter: "src1",
-							Message: xsql.Message{"id1" : 1, "f1" : "v1"},
+							Message: xsql.Message{"id1": 1, "f1": "v1"},
 						},
 					},
 				},
@@ -75,38 +74,37 @@ func TestHavingPlan_Apply(t *testing.T) {
 			sql: `SELECT id1 FROM src1 HAVING sum(id1) = 1`,
 			data: xsql.WindowTuplesSet{
 				xsql.WindowTuples{
-					Emitter:"src1",
-					Tuples:[]xsql.Tuple{
+					Emitter: "src1",
+					Tuples: []xsql.Tuple{
 						{
 							Emitter: "src1",
-							Message: xsql.Message{"id1" : 1, "f1" : "v1"},
+							Message: xsql.Message{"id1": 1, "f1": "v1"},
 						},
 					},
 				},
 			},
 			result: xsql.WindowTuplesSet{
 				xsql.WindowTuples{
-					Emitter:"src1",
-					Tuples:[]xsql.Tuple{
+					Emitter: "src1",
+					Tuples: []xsql.Tuple{
 						{
 							Emitter: "src1",
-							Message: xsql.Message{"id1" : 1, "f1" : "v1"},
+							Message: xsql.Message{"id1": 1, "f1": "v1"},
 						},
 					},
 				},
 			},
 		},
 
-
 		{
 			sql: `SELECT id1 FROM src1 HAVING max(id1) > 10`,
 			data: xsql.WindowTuplesSet{
 				xsql.WindowTuples{
-					Emitter:"src1",
-					Tuples:[]xsql.Tuple{
+					Emitter: "src1",
+					Tuples: []xsql.Tuple{
 						{
 							Emitter: "src1",
-							Message: xsql.Message{"id1" : 1, "f1" : "v1"},
+							Message: xsql.Message{"id1": 1, "f1": "v1"},
 						},
 					},
 				},
@@ -118,22 +116,22 @@ func TestHavingPlan_Apply(t *testing.T) {
 			sql: `SELECT id1 FROM src1 HAVING max(id1) = 1`,
 			data: xsql.WindowTuplesSet{
 				xsql.WindowTuples{
-					Emitter:"src1",
-					Tuples:[]xsql.Tuple{
+					Emitter: "src1",
+					Tuples: []xsql.Tuple{
 						{
 							Emitter: "src1",
-							Message: xsql.Message{"id1" : 1, "f1" : "v1"},
+							Message: xsql.Message{"id1": 1, "f1": "v1"},
 						},
 					},
 				},
 			},
 			result: xsql.WindowTuplesSet{
 				xsql.WindowTuples{
-					Emitter:"src1",
-					Tuples:[]xsql.Tuple{
+					Emitter: "src1",
+					Tuples: []xsql.Tuple{
 						{
 							Emitter: "src1",
-							Message: xsql.Message{"id1" : 1, "f1" : "v1"},
+							Message: xsql.Message{"id1": 1, "f1": "v1"},
 						},
 					},
 				},
@@ -151,7 +149,7 @@ func TestHavingPlan_Apply(t *testing.T) {
 			break
 		}
 
-		pp := &HavingPlan{Condition:stmt.Having}
+		pp := &HavingPlan{Condition: stmt.Having}
 		result := pp.Apply(ctx, tt.data)
 		if !reflect.DeepEqual(tt.result, result) {
 			t.Errorf("%d. %q\n\nresult mismatch:\n\nexp=%#v\n\ngot=%#v\n\n", i, tt.sql, tt.result, result)

+ 118 - 118
xsql/plans/join_multi_test.go

@@ -20,40 +20,40 @@ func TestMultiJoinPlan_Apply(t *testing.T) {
 			sql: "SELECT id1 FROM src1 left join src2 on src1.id1 = src2.id2 left join src3 on src2.id2 = src3.id3",
 			data: xsql.WindowTuplesSet{
 				xsql.WindowTuples{
-					Emitter:"src1",
-					Tuples:[]xsql.Tuple{
+					Emitter: "src1",
+					Tuples: []xsql.Tuple{
 						{
 							Emitter: "src1",
-							Message: xsql.Message{ "id1" : 1, "f1" : "v1" },
-						},{
+							Message: xsql.Message{"id1": 1, "f1": "v1"},
+						}, {
 							Emitter: "src1",
-							Message: xsql.Message{ "id1" : 3, "f1" : "v3" },
+							Message: xsql.Message{"id1": 3, "f1": "v3"},
 						},
 					},
 				},
 
 				xsql.WindowTuples{
-					Emitter:"src2",
-					Tuples:[]xsql.Tuple{
+					Emitter: "src2",
+					Tuples: []xsql.Tuple{
 						{
 							Emitter: "src2",
-							Message: xsql.Message{ "id2" : 1, "f2" : "w1" },
-						},{
+							Message: xsql.Message{"id2": 1, "f2": "w1"},
+						}, {
 							Emitter: "src2",
-							Message: xsql.Message{ "id2" : 4, "f2" : "w3" },
+							Message: xsql.Message{"id2": 4, "f2": "w3"},
 						},
 					},
 				},
 
 				xsql.WindowTuples{
-					Emitter:"src3",
-					Tuples:[]xsql.Tuple{
+					Emitter: "src3",
+					Tuples: []xsql.Tuple{
 						{
 							Emitter: "src3",
-							Message: xsql.Message{ "id3" : 1, "f3" : "x1" },
-						},{
+							Message: xsql.Message{"id3": 1, "f3": "x1"},
+						}, {
 							Emitter: "src3",
-							Message: xsql.Message{ "id3" : 5, "f3" : "x5" },
+							Message: xsql.Message{"id3": 5, "f3": "x5"},
 						},
 					},
 				},
@@ -61,15 +61,15 @@ func TestMultiJoinPlan_Apply(t *testing.T) {
 			result: xsql.JoinTupleSets{
 				xsql.JoinTuple{
 					Tuples: []xsql.Tuple{
-						{Emitter: "src1", Message: xsql.Message{ "id1" : 1, "f1" : "v1" },},
-						{Emitter: "src2", Message: xsql.Message{ "id2" : 1, "f2" : "w1" },},
-						{Emitter: "src3", Message: xsql.Message{ "id3" : 1, "f3" : "x1" },},
+						{Emitter: "src1", Message: xsql.Message{"id1": 1, "f1": "v1"}},
+						{Emitter: "src2", Message: xsql.Message{"id2": 1, "f2": "w1"}},
+						{Emitter: "src3", Message: xsql.Message{"id3": 1, "f3": "x1"}},
 					},
 				},
 
 				xsql.JoinTuple{
 					Tuples: []xsql.Tuple{
-						{Emitter: "src1", Message: xsql.Message{ "id1" : 3, "f1" : "v3" },},
+						{Emitter: "src1", Message: xsql.Message{"id1": 3, "f1": "v3"}},
 					},
 				},
 			},
@@ -79,40 +79,40 @@ func TestMultiJoinPlan_Apply(t *testing.T) {
 			sql: "SELECT id1 FROM src1 left join src2 on src1.id1 = src2.id2 inner join src3 on src2.id2 = src3.id3",
 			data: xsql.WindowTuplesSet{
 				xsql.WindowTuples{
-					Emitter:"src1",
-					Tuples:[]xsql.Tuple{
+					Emitter: "src1",
+					Tuples: []xsql.Tuple{
 						{
 							Emitter: "src1",
-							Message: xsql.Message{ "id1" : 1, "f1" : "v1" },
-						},{
+							Message: xsql.Message{"id1": 1, "f1": "v1"},
+						}, {
 							Emitter: "src1",
-							Message: xsql.Message{ "id1" : 3, "f1" : "v3" },
+							Message: xsql.Message{"id1": 3, "f1": "v3"},
 						},
 					},
 				},
 
 				xsql.WindowTuples{
-					Emitter:"src2",
-					Tuples:[]xsql.Tuple{
+					Emitter: "src2",
+					Tuples: []xsql.Tuple{
 						{
 							Emitter: "src2",
-							Message: xsql.Message{ "id2" : 1, "f2" : "w1" },
-						},{
+							Message: xsql.Message{"id2": 1, "f2": "w1"},
+						}, {
 							Emitter: "src2",
-							Message: xsql.Message{ "id2" : 4, "f2" : "w3" },
+							Message: xsql.Message{"id2": 4, "f2": "w3"},
 						},
 					},
 				},
 
 				xsql.WindowTuples{
-					Emitter:"src3",
-					Tuples:[]xsql.Tuple{
+					Emitter: "src3",
+					Tuples: []xsql.Tuple{
 						{
 							Emitter: "src3",
-							Message: xsql.Message{ "id3" : 1, "f3" : "x1" },
-						},{
+							Message: xsql.Message{"id3": 1, "f3": "x1"},
+						}, {
 							Emitter: "src3",
-							Message: xsql.Message{ "id3" : 5, "f3" : "x5" },
+							Message: xsql.Message{"id3": 5, "f3": "x5"},
 						},
 					},
 				},
@@ -120,9 +120,9 @@ func TestMultiJoinPlan_Apply(t *testing.T) {
 			result: xsql.JoinTupleSets{
 				xsql.JoinTuple{
 					Tuples: []xsql.Tuple{
-						{Emitter: "src1", Message: xsql.Message{ "id1" : 1, "f1" : "v1" },},
-						{Emitter: "src2", Message: xsql.Message{ "id2" : 1, "f2" : "w1" },},
-						{Emitter: "src3", Message: xsql.Message{ "id3" : 1, "f3" : "x1" },},
+						{Emitter: "src1", Message: xsql.Message{"id1": 1, "f1": "v1"}},
+						{Emitter: "src2", Message: xsql.Message{"id2": 1, "f2": "w1"}},
+						{Emitter: "src3", Message: xsql.Message{"id3": 1, "f3": "x1"}},
 					},
 				},
 			},
@@ -132,40 +132,40 @@ func TestMultiJoinPlan_Apply(t *testing.T) {
 			sql: "SELECT id1 FROM src1 left join src2 on src1.id1 = src2.id2 inner join src3 on src1.id1 = src3.id3",
 			data: xsql.WindowTuplesSet{
 				xsql.WindowTuples{
-					Emitter:"src1",
-					Tuples:[]xsql.Tuple{
+					Emitter: "src1",
+					Tuples: []xsql.Tuple{
 						{
 							Emitter: "src1",
-							Message: xsql.Message{ "id1" : 1, "f1" : "v1" },
-						},{
+							Message: xsql.Message{"id1": 1, "f1": "v1"},
+						}, {
 							Emitter: "src1",
-							Message: xsql.Message{ "id1" : 5, "f1" : "v5" },
+							Message: xsql.Message{"id1": 5, "f1": "v5"},
 						},
 					},
 				},
 
 				xsql.WindowTuples{
-					Emitter:"src2",
-					Tuples:[]xsql.Tuple{
+					Emitter: "src2",
+					Tuples: []xsql.Tuple{
 						{
 							Emitter: "src2",
-							Message: xsql.Message{ "id2" : 1, "f2" : "w1" },
-						},{
+							Message: xsql.Message{"id2": 1, "f2": "w1"},
+						}, {
 							Emitter: "src2",
-							Message: xsql.Message{ "id2" : 4, "f2" : "w3" },
+							Message: xsql.Message{"id2": 4, "f2": "w3"},
 						},
 					},
 				},
 
 				xsql.WindowTuples{
-					Emitter:"src3",
-					Tuples:[]xsql.Tuple{
+					Emitter: "src3",
+					Tuples: []xsql.Tuple{
 						{
 							Emitter: "src3",
-							Message: xsql.Message{ "id3" : 2, "f3" : "x1" },
-						},{
+							Message: xsql.Message{"id3": 2, "f3": "x1"},
+						}, {
 							Emitter: "src3",
-							Message: xsql.Message{ "id3" : 5, "f3" : "x5" },
+							Message: xsql.Message{"id3": 5, "f3": "x5"},
 						},
 					},
 				},
@@ -173,8 +173,8 @@ func TestMultiJoinPlan_Apply(t *testing.T) {
 			result: xsql.JoinTupleSets{
 				xsql.JoinTuple{
 					Tuples: []xsql.Tuple{
-						{Emitter: "src1", Message: xsql.Message{ "id1" : 5, "f1" : "v5" },},
-						{Emitter: "src3", Message: xsql.Message{ "id3" : 5, "f3" : "x5" },},
+						{Emitter: "src1", Message: xsql.Message{"id1": 5, "f1": "v5"}},
+						{Emitter: "src3", Message: xsql.Message{"id3": 5, "f3": "x5"}},
 					},
 				},
 			},
@@ -184,40 +184,40 @@ func TestMultiJoinPlan_Apply(t *testing.T) {
 			sql: "SELECT id1 FROM src1 left join src2 on src1.id1 = src2.id2 full join src3 on src1.id1 = src3.id3",
 			data: xsql.WindowTuplesSet{
 				xsql.WindowTuples{
-					Emitter:"src1",
-					Tuples:[]xsql.Tuple{
+					Emitter: "src1",
+					Tuples: []xsql.Tuple{
 						{
 							Emitter: "src1",
-							Message: xsql.Message{ "id1" : 1, "f1" : "v1" },
-						},{
+							Message: xsql.Message{"id1": 1, "f1": "v1"},
+						}, {
 							Emitter: "src1",
-							Message: xsql.Message{ "id1" : 5, "f1" : "v5" },
+							Message: xsql.Message{"id1": 5, "f1": "v5"},
 						},
 					},
 				},
 
 				xsql.WindowTuples{
-					Emitter:"src2",
-					Tuples:[]xsql.Tuple{
+					Emitter: "src2",
+					Tuples: []xsql.Tuple{
 						{
 							Emitter: "src2",
-							Message: xsql.Message{ "id2" : 1, "f2" : "w1" },
-						},{
+							Message: xsql.Message{"id2": 1, "f2": "w1"},
+						}, {
 							Emitter: "src2",
-							Message: xsql.Message{ "id2" : 4, "f2" : "w3" },
+							Message: xsql.Message{"id2": 4, "f2": "w3"},
 						},
 					},
 				},
 
 				xsql.WindowTuples{
-					Emitter:"src3",
-					Tuples:[]xsql.Tuple{
+					Emitter: "src3",
+					Tuples: []xsql.Tuple{
 						{
 							Emitter: "src3",
-							Message: xsql.Message{ "id3" : 2, "f3" : "x1" },
-						},{
+							Message: xsql.Message{"id3": 2, "f3": "x1"},
+						}, {
 							Emitter: "src3",
-							Message: xsql.Message{ "id3" : 5, "f3" : "x5" },
+							Message: xsql.Message{"id3": 5, "f3": "x5"},
 						},
 					},
 				},
@@ -225,21 +225,21 @@ func TestMultiJoinPlan_Apply(t *testing.T) {
 			result: xsql.JoinTupleSets{
 				xsql.JoinTuple{
 					Tuples: []xsql.Tuple{
-						{Emitter: "src1", Message: xsql.Message{ "id1" : 1, "f1" : "v1" },},
-						{Emitter: "src2", Message: xsql.Message{ "id2" : 1, "f2" : "w1" },},
+						{Emitter: "src1", Message: xsql.Message{"id1": 1, "f1": "v1"}},
+						{Emitter: "src2", Message: xsql.Message{"id2": 1, "f2": "w1"}},
 					},
 				},
 
 				xsql.JoinTuple{
 					Tuples: []xsql.Tuple{
-						{Emitter: "src1", Message: xsql.Message{ "id1" : 5, "f1" : "v5" },},
-						{Emitter: "src3", Message: xsql.Message{ "id3" : 5, "f3" : "x5" },},
+						{Emitter: "src1", Message: xsql.Message{"id1": 5, "f1": "v5"}},
+						{Emitter: "src3", Message: xsql.Message{"id3": 5, "f3": "x5"}},
 					},
 				},
 
 				xsql.JoinTuple{
 					Tuples: []xsql.Tuple{
-						{Emitter: "src3", Message: xsql.Message{ "id3" : 2, "f3" : "x1" },},
+						{Emitter: "src3", Message: xsql.Message{"id3": 2, "f3": "x1"}},
 					},
 				},
 			},
@@ -249,40 +249,40 @@ func TestMultiJoinPlan_Apply(t *testing.T) {
 			sql: "SELECT id1 FROM src1 left join src2 on src1.id1 = src2.id2 right join src3 on src2.id2 = src3.id3",
 			data: xsql.WindowTuplesSet{
 				xsql.WindowTuples{
-					Emitter:"src1",
-					Tuples:[]xsql.Tuple{
+					Emitter: "src1",
+					Tuples: []xsql.Tuple{
 						{
 							Emitter: "src1",
-							Message: xsql.Message{ "id1" : 1, "f1" : "v1" },
-						},{
+							Message: xsql.Message{"id1": 1, "f1": "v1"},
+						}, {
 							Emitter: "src1",
-							Message: xsql.Message{ "id1" : 3, "f1" : "v3" },
+							Message: xsql.Message{"id1": 3, "f1": "v3"},
 						},
 					},
 				},
 
 				xsql.WindowTuples{
-					Emitter:"src2",
-					Tuples:[]xsql.Tuple{
+					Emitter: "src2",
+					Tuples: []xsql.Tuple{
 						{
 							Emitter: "src2",
-							Message: xsql.Message{ "id2" : 1, "f2" : "w1" },
-						},{
+							Message: xsql.Message{"id2": 1, "f2": "w1"},
+						}, {
 							Emitter: "src2",
-							Message: xsql.Message{ "id2" : 4, "f2" : "w3" },
+							Message: xsql.Message{"id2": 4, "f2": "w3"},
 						},
 					},
 				},
 
 				xsql.WindowTuples{
-					Emitter:"src3",
-					Tuples:[]xsql.Tuple{
+					Emitter: "src3",
+					Tuples: []xsql.Tuple{
 						{
 							Emitter: "src3",
-							Message: xsql.Message{ "id3" : 1, "f3" : "x1" },
-						},{
+							Message: xsql.Message{"id3": 1, "f3": "x1"},
+						}, {
 							Emitter: "src3",
-							Message: xsql.Message{ "id3" : 5, "f3" : "x5" },
+							Message: xsql.Message{"id3": 5, "f3": "x5"},
 						},
 					},
 				},
@@ -290,15 +290,15 @@ func TestMultiJoinPlan_Apply(t *testing.T) {
 			result: xsql.JoinTupleSets{
 				xsql.JoinTuple{
 					Tuples: []xsql.Tuple{
-						{Emitter: "src3", Message: xsql.Message{ "id3" : 1, "f3" : "x1" },},
-						{Emitter: "src1", Message: xsql.Message{ "id1" : 1, "f1" : "v1" },},
-						{Emitter: "src2", Message: xsql.Message{ "id2" : 1, "f2" : "w1" },},
+						{Emitter: "src3", Message: xsql.Message{"id3": 1, "f3": "x1"}},
+						{Emitter: "src1", Message: xsql.Message{"id1": 1, "f1": "v1"}},
+						{Emitter: "src2", Message: xsql.Message{"id2": 1, "f2": "w1"}},
 					},
 				},
 
 				xsql.JoinTuple{
 					Tuples: []xsql.Tuple{
-						{Emitter: "src3", Message: xsql.Message{ "id3" : 5, "f3" : "x5" },},
+						{Emitter: "src3", Message: xsql.Message{"id3": 5, "f3": "x5"}},
 					},
 				},
 			},
@@ -308,40 +308,40 @@ func TestMultiJoinPlan_Apply(t *testing.T) {
 			sql: "SELECT id1 FROM src1 left join src2 on src1.id1 = src2.id2 cross join src3",
 			data: xsql.WindowTuplesSet{
 				xsql.WindowTuples{
-					Emitter:"src1",
-					Tuples:[]xsql.Tuple{
+					Emitter: "src1",
+					Tuples: []xsql.Tuple{
 						{
 							Emitter: "src1",
-							Message: xsql.Message{ "id1" : 1, "f1" : "v1" },
-						},{
+							Message: xsql.Message{"id1": 1, "f1": "v1"},
+						}, {
 							Emitter: "src1",
-							Message: xsql.Message{ "id1" : 5, "f1" : "v5" },
+							Message: xsql.Message{"id1": 5, "f1": "v5"},
 						},
 					},
 				},
 
 				xsql.WindowTuples{
-					Emitter:"src2",
-					Tuples:[]xsql.Tuple{
+					Emitter: "src2",
+					Tuples: []xsql.Tuple{
 						{
 							Emitter: "src2",
-							Message: xsql.Message{ "id2" : 1, "f2" : "w1" },
-						},{
+							Message: xsql.Message{"id2": 1, "f2": "w1"},
+						}, {
 							Emitter: "src2",
-							Message: xsql.Message{ "id2" : 4, "f2" : "w3" },
+							Message: xsql.Message{"id2": 4, "f2": "w3"},
 						},
 					},
 				},
 
 				xsql.WindowTuples{
-					Emitter:"src3",
-					Tuples:[]xsql.Tuple{
+					Emitter: "src3",
+					Tuples: []xsql.Tuple{
 						{
 							Emitter: "src3",
-							Message: xsql.Message{ "id3" : 2, "f3" : "x1" },
-						},{
+							Message: xsql.Message{"id3": 2, "f3": "x1"},
+						}, {
 							Emitter: "src3",
-							Message: xsql.Message{ "id3" : 5, "f3" : "x5" },
+							Message: xsql.Message{"id3": 5, "f3": "x5"},
 						},
 					},
 				},
@@ -349,10 +349,10 @@ func TestMultiJoinPlan_Apply(t *testing.T) {
 			result: xsql.JoinTupleSets{
 				xsql.JoinTuple{
 					Tuples: []xsql.Tuple{
-						{Emitter: "src1", Message: xsql.Message{ "id1" : 1, "f1" : "v1" },},
-						{Emitter: "src2", Message: xsql.Message{ "id2" : 1, "f2" : "w1" },},
-						{Emitter: "src3", Message: xsql.Message{ "id3" : 2, "f3" : "x1" },},
-						{Emitter: "src3", Message: xsql.Message{ "id3" : 5, "f3" : "x5" },},
+						{Emitter: "src1", Message: xsql.Message{"id1": 1, "f1": "v1"}},
+						{Emitter: "src2", Message: xsql.Message{"id2": 1, "f2": "w1"}},
+						{Emitter: "src3", Message: xsql.Message{"id3": 2, "f3": "x1"}},
+						{Emitter: "src3", Message: xsql.Message{"id3": 5, "f3": "x5"}},
 					},
 				},
 
@@ -366,9 +366,9 @@ func TestMultiJoinPlan_Apply(t *testing.T) {
 
 				xsql.JoinTuple{
 					Tuples: []xsql.Tuple{
-						{Emitter: "src1", Message: xsql.Message{ "id1" : 5, "f1" : "v5" },},
-						{Emitter: "src3", Message: xsql.Message{ "id3" : 2, "f3" : "x1" },},
-						{Emitter: "src3", Message: xsql.Message{ "id3" : 5, "f3" : "x5" },},
+						{Emitter: "src1", Message: xsql.Message{"id1": 5, "f1": "v5"}},
+						{Emitter: "src3", Message: xsql.Message{"id3": 2, "f3": "x1"}},
+						{Emitter: "src3", Message: xsql.Message{"id3": 5, "f3": "x5"}},
 					},
 				},
 
@@ -393,9 +393,9 @@ func TestMultiJoinPlan_Apply(t *testing.T) {
 			break
 		}
 
-		if table, ok := stmt.Sources[0].(*xsql.Table); !ok{
+		if table, ok := stmt.Sources[0].(*xsql.Table); !ok {
 			t.Errorf("statement source is not a table")
-		}else{
+		} else {
 			pp := &JoinPlan{Joins: stmt.Joins, From: table}
 			result := pp.Apply(ctx, tt.data)
 			if !reflect.DeepEqual(tt.result, result) {
@@ -403,4 +403,4 @@ func TestMultiJoinPlan_Apply(t *testing.T) {
 			}
 		}
 	}
-}
+}

+ 10 - 11
xsql/plans/join_operator.go

@@ -1,15 +1,15 @@
 package plans
 
 import (
+	"fmt"
 	"github.com/emqx/kuiper/common"
 	"github.com/emqx/kuiper/xsql"
 	"github.com/emqx/kuiper/xstream/api"
-	"fmt"
 )
 
 //TODO join expr should only be the equal op between 2 streams like tb1.id = tb2.id
 type JoinPlan struct {
-	From *xsql.Table
+	From  *xsql.Table
 	Joins xsql.Joins
 }
 
@@ -53,7 +53,7 @@ func (jp *JoinPlan) Apply(ctx api.StreamContext, data interface{}) interface{} {
 func getStreamNames(join *xsql.Join) ([]string, error) {
 	var srcs []string
 	xsql.WalkFunc(join, func(node xsql.Node) {
-		if f,ok := node.(*xsql.FieldRef); ok {
+		if f, ok := node.(*xsql.FieldRef); ok {
 			if string(f.StreamName) == "" {
 				return
 			}
@@ -120,7 +120,7 @@ func (jp *JoinPlan) evalSet(input xsql.WindowTuplesSet, join xsql.Join) (xsql.Jo
 							merged.AddTuple(right)
 							sets = append(sets, *merged)
 							merged = &xsql.JoinTuple{}
-						}else{
+						} else {
 							merged.AddTuple(right)
 						}
 					}
@@ -168,7 +168,7 @@ func (jp *JoinPlan) evalSetWithRightJoin(input xsql.WindowTuplesSet, join xsql.J
 			temp.AddTuple(right)
 			temp.AddTuple(left)
 			ve := &xsql.ValuerEval{Valuer: xsql.MultiValuer(temp, &xsql.FunctionValuer{})}
-			if r, ok  := ve.Eval(join.Expr).(bool); ok {
+			if r, ok := ve.Eval(join.Expr).(bool); ok {
 				if r {
 					merged.AddTuple(left)
 					isJoint = true
@@ -190,8 +190,7 @@ func (jp *JoinPlan) evalSetWithRightJoin(input xsql.WindowTuplesSet, join xsql.J
 	return sets, nil
 }
 
-
-func (jp *JoinPlan) evalJoinSets(set *xsql.JoinTupleSets, input xsql.WindowTuplesSet, join xsql.Join) (interface{}, error)  {
+func (jp *JoinPlan) evalJoinSets(set *xsql.JoinTupleSets, input xsql.WindowTuplesSet, join xsql.Join) (interface{}, error) {
 	var rightStream string
 	if join.Alias == "" {
 		rightStream = join.Name
@@ -203,7 +202,7 @@ func (jp *JoinPlan) evalJoinSets(set *xsql.JoinTupleSets, input xsql.WindowTuple
 
 	newSets := xsql.JoinTupleSets{}
 	if join.JoinType == xsql.RIGHT_JOIN {
-		return jp.evalRightJoinSets(set, input, join,false)
+		return jp.evalRightJoinSets(set, input, join, false)
 	}
 	for _, left := range *set {
 		merged := &xsql.JoinTuple{}
@@ -244,7 +243,7 @@ func (jp *JoinPlan) evalJoinSets(set *xsql.JoinTupleSets, input xsql.WindowTuple
 	return newSets, nil
 }
 
-func (jp *JoinPlan) evalRightJoinSets(set *xsql.JoinTupleSets, input xsql.WindowTuplesSet, join xsql.Join, excludeJoint bool) (xsql.JoinTupleSets, error)  {
+func (jp *JoinPlan) evalRightJoinSets(set *xsql.JoinTupleSets, input xsql.WindowTuplesSet, join xsql.Join, excludeJoint bool) (xsql.JoinTupleSets, error) {
 	var rightStream string
 	if join.Alias == "" {
 		rightStream = join.Name
@@ -270,7 +269,7 @@ func (jp *JoinPlan) evalRightJoinSets(set *xsql.JoinTupleSets, input xsql.Window
 		}
 
 		if excludeJoint {
-			if len(merged.Tuples) > 0  && (!isJoint) {
+			if len(merged.Tuples) > 0 && (!isJoint) {
 				newSets = append(newSets, *merged)
 			}
 		} else {
@@ -280,4 +279,4 @@ func (jp *JoinPlan) evalRightJoinSets(set *xsql.JoinTupleSets, input xsql.Window
 		}
 	}
 	return newSets, nil
-}
+}

文件差异内容过多而无法显示
+ 457 - 478
xsql/plans/join_test.go


+ 8 - 9
xsql/plans/math_func_test.go

@@ -13,8 +13,8 @@ import (
 
 func TestMathAndConversionFunc_Apply1(t *testing.T) {
 	var tests = []struct {
-		sql  string
-		data *xsql.Tuple
+		sql    string
+		data   *xsql.Tuple
 		result []map[string]interface{}
 	}{
 		{
@@ -22,7 +22,7 @@ func TestMathAndConversionFunc_Apply1(t *testing.T) {
 			data: &xsql.Tuple{
 				Emitter: "test",
 				Message: xsql.Message{
-					"a" : -1,
+					"a": -1,
 				},
 			},
 			result: []map[string]interface{}{{
@@ -35,7 +35,7 @@ func TestMathAndConversionFunc_Apply1(t *testing.T) {
 			data: &xsql.Tuple{
 				Emitter: "test",
 				Message: xsql.Message{
-					"a" : -1.1,
+					"a": -1.1,
 				},
 			},
 			result: []map[string]interface{}{{
@@ -48,7 +48,7 @@ func TestMathAndConversionFunc_Apply1(t *testing.T) {
 			data: &xsql.Tuple{
 				Emitter: "test",
 				Message: xsql.Message{
-					"a" : 1.1,
+					"a": 1.1,
 				},
 			},
 			result: []map[string]interface{}{{
@@ -451,7 +451,6 @@ func TestMathAndConversionFunc_Apply1(t *testing.T) {
 				"a": float64(3.00),
 			}},
 		},
-
 	}
 
 	fmt.Printf("The test bucket size is %d.\n\n", len(tests))
@@ -462,11 +461,11 @@ func TestMathAndConversionFunc_Apply1(t *testing.T) {
 		stmt, err := xsql.NewParser(strings.NewReader(tt.sql)).Parse()
 		if err != nil && tt.result == nil {
 			continue
-		} else if err != nil && tt.result != nil{
+		} else if err != nil && tt.result != nil {
 			t.Errorf("%q", err)
 			continue
 		}
-		pp := &ProjectPlan{Fields:stmt.Fields}
+		pp := &ProjectPlan{Fields: stmt.Fields}
 		pp.isTest = true
 		result := pp.Apply(ctx, tt.data)
 		var mapRes []map[string]interface{}
@@ -485,4 +484,4 @@ func TestMathAndConversionFunc_Apply1(t *testing.T) {
 			t.Errorf("The returned result is not type of []byte\n")
 		}
 	}
-}
+}

+ 30 - 31
xsql/plans/misc_func_test.go

@@ -13,8 +13,8 @@ import (
 
 func TestHashFunc_Apply1(t *testing.T) {
 	var tests = []struct {
-		sql  string
-		data *xsql.Tuple
+		sql    string
+		data   *xsql.Tuple
 		result []map[string]interface{}
 	}{
 		{
@@ -22,9 +22,9 @@ func TestHashFunc_Apply1(t *testing.T) {
 			data: &xsql.Tuple{
 				Emitter: "test",
 				Message: xsql.Message{
-					"a" : "The quick brown fox jumps over the lazy dog",
-					"b" : "myb",
-					"c" : "myc",
+					"a": "The quick brown fox jumps over the lazy dog",
+					"b": "myb",
+					"c": "myc",
 				},
 			},
 			result: []map[string]interface{}{{
@@ -36,9 +36,9 @@ func TestHashFunc_Apply1(t *testing.T) {
 			data: &xsql.Tuple{
 				Emitter: "test",
 				Message: xsql.Message{
-					"a" : "The quick brown fox jumps over the lazy dog",
-					"b" : "myb",
-					"c" : "myc",
+					"a": "The quick brown fox jumps over the lazy dog",
+					"b": "myb",
+					"c": "myc",
 				},
 			},
 			result: []map[string]interface{}{{
@@ -50,9 +50,9 @@ func TestHashFunc_Apply1(t *testing.T) {
 			data: &xsql.Tuple{
 				Emitter: "test",
 				Message: xsql.Message{
-					"a" : "The quick brown fox jumps over the lazy dog",
-					"b" : "myb",
-					"c" : "myc",
+					"a": "The quick brown fox jumps over the lazy dog",
+					"b": "myb",
+					"c": "myc",
 				},
 			},
 			result: []map[string]interface{}{{
@@ -64,9 +64,9 @@ func TestHashFunc_Apply1(t *testing.T) {
 			data: &xsql.Tuple{
 				Emitter: "test",
 				Message: xsql.Message{
-					"a" : "The quick brown fox jumps over the lazy dog",
-					"b" : "myb",
-					"c" : "myc",
+					"a": "The quick brown fox jumps over the lazy dog",
+					"b": "myb",
+					"c": "myc",
 				},
 			},
 			result: []map[string]interface{}{{
@@ -78,9 +78,9 @@ func TestHashFunc_Apply1(t *testing.T) {
 			data: &xsql.Tuple{
 				Emitter: "test",
 				Message: xsql.Message{
-					"a" : "The quick brown fox jumps over the lazy dog",
-					"b" : "myb",
-					"c" : "myc",
+					"a": "The quick brown fox jumps over the lazy dog",
+					"b": "myb",
+					"c": "myc",
 				},
 			},
 			result: []map[string]interface{}{{
@@ -93,7 +93,7 @@ func TestHashFunc_Apply1(t *testing.T) {
 			data: &xsql.Tuple{
 				Emitter: "test",
 				Message: xsql.Message{
-					xsql.INTERNAL_MQTT_TOPIC_KEY : "devices/device_001/message",
+					xsql.INTERNAL_MQTT_TOPIC_KEY: "devices/device_001/message",
 				},
 			},
 			result: []map[string]interface{}{{
@@ -106,7 +106,7 @@ func TestHashFunc_Apply1(t *testing.T) {
 			data: &xsql.Tuple{
 				Emitter: "test",
 				Message: xsql.Message{
-					xsql.INTERNAL_MQTT_TOPIC_KEY : "devices/device_001/message",
+					xsql.INTERNAL_MQTT_TOPIC_KEY: "devices/device_001/message",
 				},
 			},
 			result: []map[string]interface{}{{
@@ -119,16 +119,15 @@ func TestHashFunc_Apply1(t *testing.T) {
 			data: &xsql.Tuple{
 				Emitter: "test",
 				Message: xsql.Message{
-					"topic" : "fff",
-					xsql.INTERNAL_MQTT_TOPIC_KEY : "devices/device_001/message",
+					"topic":                      "fff",
+					xsql.INTERNAL_MQTT_TOPIC_KEY: "devices/device_001/message",
 				},
 			},
 			result: []map[string]interface{}{{
 				"topic": "fff",
-				"a": "devices/device_001/message",
+				"a":     "devices/device_001/message",
 			}},
 		},
-
 	}
 
 	fmt.Printf("The test bucket size is %d.\n\n", len(tests))
@@ -139,7 +138,7 @@ func TestHashFunc_Apply1(t *testing.T) {
 		if err != nil || stmt == nil {
 			t.Errorf("parse sql %s error %v", tt.sql, err)
 		}
-		pp := &ProjectPlan{Fields:stmt.Fields}
+		pp := &ProjectPlan{Fields: stmt.Fields}
 		pp.isTest = true
 		result := pp.Apply(ctx, tt.data)
 		var mapRes []map[string]interface{}
@@ -161,8 +160,8 @@ func TestHashFunc_Apply1(t *testing.T) {
 }
 func TestMqttFunc_Apply2(t *testing.T) {
 	var tests = []struct {
-		sql  string
-		data xsql.JoinTupleSets
+		sql    string
+		data   xsql.JoinTupleSets
 		result []map[string]interface{}
 	}{
 		{
@@ -170,15 +169,15 @@ func TestMqttFunc_Apply2(t *testing.T) {
 			data: xsql.JoinTupleSets{
 				xsql.JoinTuple{
 					Tuples: []xsql.Tuple{
-						{Emitter: "src1", Message: xsql.Message{ "id1" : "1", "f1" : "v1" , xsql.INTERNAL_MQTT_TOPIC_KEY: "devices/type1/device001"},},
-						{Emitter: "src2", Message: xsql.Message{ "id2" : "1", "f2" : "w1", xsql.INTERNAL_MQTT_TOPIC_KEY: "devices/type2/device001" },},
+						{Emitter: "src1", Message: xsql.Message{"id1": "1", "f1": "v1", xsql.INTERNAL_MQTT_TOPIC_KEY: "devices/type1/device001"}},
+						{Emitter: "src2", Message: xsql.Message{"id2": "1", "f2": "w1", xsql.INTERNAL_MQTT_TOPIC_KEY: "devices/type2/device001"}},
 					},
 				},
 			},
 			result: []map[string]interface{}{{
 				"id1": "1",
-				"a": "devices/type1/device001",
-				"b": "devices/type2/device001",
+				"a":   "devices/type1/device001",
+				"b":   "devices/type2/device001",
 			}},
 		},
 	}
@@ -191,7 +190,7 @@ func TestMqttFunc_Apply2(t *testing.T) {
 		if err != nil || stmt == nil {
 			t.Errorf("parse sql %s error %v", tt.sql, err)
 		}
-		pp := &ProjectPlan{Fields:stmt.Fields}
+		pp := &ProjectPlan{Fields: stmt.Fields}
 		pp.isTest = true
 		result := pp.Apply(ctx, tt.data)
 		var mapRes []map[string]interface{}

+ 3 - 3
xsql/plans/order_operator.go

@@ -10,8 +10,8 @@ type OrderPlan struct {
 }
 
 /**
-  *  input: *xsql.Tuple from preprocessor | xsql.WindowTuplesSet from windowOp | xsql.JoinTupleSets from joinOp
-  *  output: *xsql.Tuple | xsql.WindowTuplesSet | xsql.JoinTupleSets
+ *  input: *xsql.Tuple from preprocessor | xsql.WindowTuplesSet from windowOp | xsql.JoinTupleSets from joinOp
+ *  output: *xsql.Tuple | xsql.WindowTuplesSet | xsql.JoinTupleSets
  */
 func (p *OrderPlan) Apply(ctx api.StreamContext, data interface{}) interface{} {
 	log := ctx.GetLogger()
@@ -27,4 +27,4 @@ func (p *OrderPlan) Apply(ctx api.StreamContext, data interface{}) interface{} {
 		log.Errorf("Expect xsql.Valuer or its array type.")
 		return nil
 	}
-}
+}

+ 92 - 92
xsql/plans/order_test.go

@@ -12,8 +12,8 @@ import (
 
 func TestOrderPlan_Apply(t *testing.T) {
 	var tests = []struct {
-		sql  string
-		data interface{}
+		sql    string
+		data   interface{}
 		result interface{}
 	}{
 		{
@@ -21,13 +21,13 @@ func TestOrderPlan_Apply(t *testing.T) {
 			data: &xsql.Tuple{
 				Emitter: "tbl",
 				Message: xsql.Message{
-					"abc" : int64(6),
+					"abc": int64(6),
 				},
 			},
 			result: &xsql.Tuple{
 				Emitter: "tbl",
 				Message: xsql.Message{
-					"abc" : int64(6),
+					"abc": int64(6),
 				},
 			},
 		},
@@ -37,15 +37,15 @@ func TestOrderPlan_Apply(t *testing.T) {
 			data: &xsql.Tuple{
 				Emitter: "tbl",
 				Message: xsql.Message{
-					"abc" : int64(34),
-					"def" : "hello",
+					"abc": int64(34),
+					"def": "hello",
 				},
 			},
 			result: &xsql.Tuple{
 				Emitter: "tbl",
 				Message: xsql.Message{
-					"abc" : int64(34),
-					"def" : "hello",
+					"abc": int64(34),
+					"def": "hello",
 				},
 			},
 		},
@@ -54,34 +54,34 @@ func TestOrderPlan_Apply(t *testing.T) {
 			sql: "SELECT id1 FROM src1 WHERE f1 = \"v1\" GROUP BY TUMBLINGWINDOW(ss, 10) ORDER BY id1 DESC",
 			data: xsql.WindowTuplesSet{
 				xsql.WindowTuples{
-					Emitter:"src1",
-					Tuples:[]xsql.Tuple{
+					Emitter: "src1",
+					Tuples: []xsql.Tuple{
 						{
 							Emitter: "src1",
-							Message: xsql.Message{ "id1" : 1, "f1" : "v1", },
-						},{
+							Message: xsql.Message{"id1": 1, "f1": "v1"},
+						}, {
 							Emitter: "src1",
-							Message: xsql.Message{ "id1" : 2, "f1" : "v2", },
-						},{
+							Message: xsql.Message{"id1": 2, "f1": "v2"},
+						}, {
 							Emitter: "src1",
-							Message: xsql.Message{ "id1" : 3, "f1" : "v1", },
+							Message: xsql.Message{"id1": 3, "f1": "v1"},
 						},
 					},
 				},
 			},
 			result: xsql.WindowTuplesSet{
 				xsql.WindowTuples{
-					Emitter:"src1",
-					Tuples:[]xsql.Tuple{
+					Emitter: "src1",
+					Tuples: []xsql.Tuple{
 						{
 							Emitter: "src1",
-							Message: xsql.Message{ "id1" : 3, "f1" : "v1", },
-						},{
+							Message: xsql.Message{"id1": 3, "f1": "v1"},
+						}, {
 							Emitter: "src1",
-							Message: xsql.Message{ "id1" : 2, "f1" : "v2", },
-						},{
+							Message: xsql.Message{"id1": 2, "f1": "v2"},
+						}, {
 							Emitter: "src1",
-							Message: xsql.Message{ "id1" : 1, "f1" : "v1", },
+							Message: xsql.Message{"id1": 1, "f1": "v1"},
 						},
 					},
 				},
@@ -92,34 +92,34 @@ func TestOrderPlan_Apply(t *testing.T) {
 			sql: "SELECT * FROM src1 WHERE f1 = \"v1\" GROUP BY TUMBLINGWINDOW(ss, 10) ORDER BY f1, id1 DESC",
 			data: xsql.WindowTuplesSet{
 				xsql.WindowTuples{
-					Emitter:"src1",
-					Tuples:[]xsql.Tuple{
+					Emitter: "src1",
+					Tuples: []xsql.Tuple{
 						{
 							Emitter: "src1",
-							Message: xsql.Message{ "id1" : 1, "f1" : "v1", },
-						},{
+							Message: xsql.Message{"id1": 1, "f1": "v1"},
+						}, {
 							Emitter: "src1",
-							Message: xsql.Message{ "id1" : 2, "f1" : "v2", },
-						},{
+							Message: xsql.Message{"id1": 2, "f1": "v2"},
+						}, {
 							Emitter: "src1",
-							Message: xsql.Message{ "id1" : 3, "f1" : "v1", },
+							Message: xsql.Message{"id1": 3, "f1": "v1"},
 						},
 					},
 				},
 			},
 			result: xsql.WindowTuplesSet{
 				xsql.WindowTuples{
-					Emitter:"src1",
-					Tuples:[]xsql.Tuple{
+					Emitter: "src1",
+					Tuples: []xsql.Tuple{
 						{
 							Emitter: "src1",
-							Message: xsql.Message{ "id1" : 3, "f1" : "v1", },
-						},{
+							Message: xsql.Message{"id1": 3, "f1": "v1"},
+						}, {
 							Emitter: "src1",
-							Message: xsql.Message{ "id1" : 1, "f1" : "v1", },
-						},{
+							Message: xsql.Message{"id1": 1, "f1": "v1"},
+						}, {
 							Emitter: "src1",
-							Message: xsql.Message{ "id1" : 2, "f1" : "v2", },
+							Message: xsql.Message{"id1": 2, "f1": "v2"},
 						},
 					},
 				},
@@ -129,34 +129,34 @@ func TestOrderPlan_Apply(t *testing.T) {
 			sql: "SELECT * FROM src1 GROUP BY TUMBLINGWINDOW(ss, 10) ORDER BY ts DESC",
 			data: xsql.WindowTuplesSet{
 				xsql.WindowTuples{
-					Emitter:"src1",
-					Tuples:[]xsql.Tuple{
+					Emitter: "src1",
+					Tuples: []xsql.Tuple{
 						{
 							Emitter: "src1",
-							Message: xsql.Message{ "id1" : 1, "f1" : "v1", "ts": common.TimeFromUnixMilli(1568854515000)},
-						},{
+							Message: xsql.Message{"id1": 1, "f1": "v1", "ts": common.TimeFromUnixMilli(1568854515000)},
+						}, {
 							Emitter: "src1",
-							Message: xsql.Message{ "id1" : 2, "f1" : "v2", "ts": common.TimeFromUnixMilli(1568854525000)},
-						},{
+							Message: xsql.Message{"id1": 2, "f1": "v2", "ts": common.TimeFromUnixMilli(1568854525000)},
+						}, {
 							Emitter: "src1",
-							Message: xsql.Message{ "id1" : 3, "f1" : "v1", "ts": common.TimeFromUnixMilli(1568854535000)},
+							Message: xsql.Message{"id1": 3, "f1": "v1", "ts": common.TimeFromUnixMilli(1568854535000)},
 						},
 					},
 				},
 			},
 			result: xsql.WindowTuplesSet{
 				xsql.WindowTuples{
-					Emitter:"src1",
-					Tuples:[]xsql.Tuple{
+					Emitter: "src1",
+					Tuples: []xsql.Tuple{
 						{
 							Emitter: "src1",
-							Message: xsql.Message{ "id1" : 3, "f1" : "v1", "ts": common.TimeFromUnixMilli(1568854535000)},
-						},{
+							Message: xsql.Message{"id1": 3, "f1": "v1", "ts": common.TimeFromUnixMilli(1568854535000)},
+						}, {
 							Emitter: "src1",
-							Message: xsql.Message{ "id1" : 2, "f1" : "v2", "ts": common.TimeFromUnixMilli(1568854525000)},
-						},{
+							Message: xsql.Message{"id1": 2, "f1": "v2", "ts": common.TimeFromUnixMilli(1568854525000)},
+						}, {
 							Emitter: "src1",
-							Message: xsql.Message{ "id1" : 1, "f1" : "v1", "ts": common.TimeFromUnixMilli(1568854515000)},
+							Message: xsql.Message{"id1": 1, "f1": "v1", "ts": common.TimeFromUnixMilli(1568854515000)},
 						},
 					},
 				},
@@ -168,38 +168,38 @@ func TestOrderPlan_Apply(t *testing.T) {
 			data: xsql.JoinTupleSets{
 				xsql.JoinTuple{
 					Tuples: []xsql.Tuple{
-						{Emitter: "src1", Message: xsql.Message{ "id1" : 1, "f1" : "v1",},},
-						{Emitter: "src2", Message: xsql.Message{ "id2" : 2, "f2" : "w2",},},
+						{Emitter: "src1", Message: xsql.Message{"id1": 1, "f1": "v1"}},
+						{Emitter: "src2", Message: xsql.Message{"id2": 2, "f2": "w2"}},
 					},
 				},
 				xsql.JoinTuple{
 					Tuples: []xsql.Tuple{
-						{Emitter: "src1", Message: xsql.Message{ "id1" : 2, "f1" : "v2",},},
-						{Emitter: "src2", Message: xsql.Message{ "id2" : 4, "f2" : "w3",},},
+						{Emitter: "src1", Message: xsql.Message{"id1": 2, "f1": "v2"}},
+						{Emitter: "src2", Message: xsql.Message{"id2": 4, "f2": "w3"}},
 					},
 				},
 				xsql.JoinTuple{
 					Tuples: []xsql.Tuple{
-						{Emitter: "src1", Message: xsql.Message{ "id1" : 3, "f1" : "v1",},},
+						{Emitter: "src1", Message: xsql.Message{"id1": 3, "f1": "v1"}},
 					},
 				},
 			},
 			result: xsql.JoinTupleSets{
 				xsql.JoinTuple{
 					Tuples: []xsql.Tuple{
-						{Emitter: "src1", Message: xsql.Message{ "id1" : 3, "f1" : "v1",},},
+						{Emitter: "src1", Message: xsql.Message{"id1": 3, "f1": "v1"}},
 					},
 				},
 				xsql.JoinTuple{
 					Tuples: []xsql.Tuple{
-						{Emitter: "src1", Message: xsql.Message{ "id1" : 2, "f1" : "v2",},},
-						{Emitter: "src2", Message: xsql.Message{ "id2" : 4, "f2" : "w3",},},
+						{Emitter: "src1", Message: xsql.Message{"id1": 2, "f1": "v2"}},
+						{Emitter: "src2", Message: xsql.Message{"id2": 4, "f2": "w3"}},
 					},
 				},
 				xsql.JoinTuple{
 					Tuples: []xsql.Tuple{
-						{Emitter: "src1", Message: xsql.Message{ "id1" : 1, "f1" : "v1",},},
-						{Emitter: "src2", Message: xsql.Message{ "id2" : 2, "f2" : "w2",},},
+						{Emitter: "src1", Message: xsql.Message{"id1": 1, "f1": "v1"}},
+						{Emitter: "src2", Message: xsql.Message{"id2": 2, "f2": "w2"}},
 					},
 				},
 			},
@@ -209,38 +209,38 @@ func TestOrderPlan_Apply(t *testing.T) {
 			data: xsql.JoinTupleSets{
 				xsql.JoinTuple{
 					Tuples: []xsql.Tuple{
-						{Emitter: "src1", Message: xsql.Message{ "id1" : 1, "f1" : "v1",},},
-						{Emitter: "src2", Message: xsql.Message{ "id2" : 2, "f2" : "w2",},},
+						{Emitter: "src1", Message: xsql.Message{"id1": 1, "f1": "v1"}},
+						{Emitter: "src2", Message: xsql.Message{"id2": 2, "f2": "w2"}},
 					},
 				},
 				xsql.JoinTuple{
 					Tuples: []xsql.Tuple{
-						{Emitter: "src1", Message: xsql.Message{ "id1" : 2, "f1" : "v2",},},
-						{Emitter: "src2", Message: xsql.Message{ "id2" : 4, "f2" : "w3",},},
+						{Emitter: "src1", Message: xsql.Message{"id1": 2, "f1": "v2"}},
+						{Emitter: "src2", Message: xsql.Message{"id2": 4, "f2": "w3"}},
 					},
 				},
 				xsql.JoinTuple{
 					Tuples: []xsql.Tuple{
-						{Emitter: "src1", Message: xsql.Message{ "id1" : 3, "f1" : "v1",},},
+						{Emitter: "src1", Message: xsql.Message{"id1": 3, "f1": "v1"}},
 					},
 				},
 			},
 			result: xsql.JoinTupleSets{
 				xsql.JoinTuple{
 					Tuples: []xsql.Tuple{
-						{Emitter: "src1", Message: xsql.Message{ "id1" : 1, "f1" : "v1",},},
-						{Emitter: "src2", Message: xsql.Message{ "id2" : 2, "f2" : "w2",},},
+						{Emitter: "src1", Message: xsql.Message{"id1": 1, "f1": "v1"}},
+						{Emitter: "src2", Message: xsql.Message{"id2": 2, "f2": "w2"}},
 					},
 				},
 				xsql.JoinTuple{
 					Tuples: []xsql.Tuple{
-						{Emitter: "src1", Message: xsql.Message{ "id1" : 2, "f1" : "v2",},},
-						{Emitter: "src2", Message: xsql.Message{ "id2" : 4, "f2" : "w3",},},
+						{Emitter: "src1", Message: xsql.Message{"id1": 2, "f1": "v2"}},
+						{Emitter: "src2", Message: xsql.Message{"id2": 4, "f2": "w3"}},
 					},
 				},
 				xsql.JoinTuple{
 					Tuples: []xsql.Tuple{
-						{Emitter: "src1", Message: xsql.Message{ "id1" : 3, "f1" : "v1",},},
+						{Emitter: "src1", Message: xsql.Message{"id1": 3, "f1": "v1"}},
 					},
 				},
 			},
@@ -253,19 +253,19 @@ func TestOrderPlan_Apply(t *testing.T) {
 					&xsql.Tuple{
 						Emitter: "tbl",
 						Message: xsql.Message{
-							"abc" : int64(6),
-							"def" : "hello",
+							"abc": int64(6),
+							"def": "hello",
 						},
 					},
 				},
 			},
-			result:xsql.GroupedTuplesSet{
+			result: xsql.GroupedTuplesSet{
 				{
 					&xsql.Tuple{
 						Emitter: "tbl",
 						Message: xsql.Message{
-							"abc" : int64(6),
-							"def" : "hello",
+							"abc": int64(6),
+							"def": "hello",
 						},
 					},
 				},
@@ -277,17 +277,17 @@ func TestOrderPlan_Apply(t *testing.T) {
 				{
 					&xsql.Tuple{
 						Emitter: "src1",
-						Message: xsql.Message{ "id1" : 1, "f1" : "v1", },
+						Message: xsql.Message{"id1": 1, "f1": "v1"},
 					},
 					&xsql.Tuple{
 						Emitter: "src1",
-						Message: xsql.Message{ "id1" : 3, "f1" : "v1", },
+						Message: xsql.Message{"id1": 3, "f1": "v1"},
 					},
 				},
 				{
 					&xsql.Tuple{
 						Emitter: "src1",
-						Message: xsql.Message{ "id1" : 2, "f1" : "v2", },
+						Message: xsql.Message{"id1": 2, "f1": "v2"},
 					},
 				},
 			},
@@ -295,17 +295,17 @@ func TestOrderPlan_Apply(t *testing.T) {
 				{
 					&xsql.Tuple{
 						Emitter: "src1",
-						Message: xsql.Message{ "id1" : 2, "f1" : "v2", },
+						Message: xsql.Message{"id1": 2, "f1": "v2"},
 					},
 				},
 				{
 					&xsql.Tuple{
 						Emitter: "src1",
-						Message: xsql.Message{ "id1" : 1, "f1" : "v1", },
+						Message: xsql.Message{"id1": 1, "f1": "v1"},
 					},
 					&xsql.Tuple{
 						Emitter: "src1",
-						Message: xsql.Message{ "id1" : 3, "f1" : "v1", },
+						Message: xsql.Message{"id1": 3, "f1": "v1"},
 					},
 				},
 			},
@@ -316,23 +316,23 @@ func TestOrderPlan_Apply(t *testing.T) {
 				{
 					&xsql.JoinTuple{
 						Tuples: []xsql.Tuple{
-							{Emitter: "src1", Message: xsql.Message{ "id1" : 1, "f1" : "v1",},},
-							{Emitter: "src2", Message: xsql.Message{ "id2" : 2, "f2" : "w2",},},
+							{Emitter: "src1", Message: xsql.Message{"id1": 1, "f1": "v1"}},
+							{Emitter: "src2", Message: xsql.Message{"id2": 2, "f2": "w2"}},
 						},
 					},
 				},
 				{
 					&xsql.JoinTuple{
 						Tuples: []xsql.Tuple{
-							{Emitter: "src1", Message: xsql.Message{ "id1" : 2, "f1" : "v2",},},
-							{Emitter: "src2", Message: xsql.Message{ "id2" : 4, "f2" : "w3",},},
+							{Emitter: "src1", Message: xsql.Message{"id1": 2, "f1": "v2"}},
+							{Emitter: "src2", Message: xsql.Message{"id2": 4, "f2": "w3"}},
 						},
 					},
 				},
 				{
 					&xsql.JoinTuple{
 						Tuples: []xsql.Tuple{
-							{Emitter: "src1", Message: xsql.Message{ "id1" : 3, "f1" : "v1",},},
+							{Emitter: "src1", Message: xsql.Message{"id1": 3, "f1": "v1"}},
 						},
 					},
 				},
@@ -341,23 +341,23 @@ func TestOrderPlan_Apply(t *testing.T) {
 				{
 					&xsql.JoinTuple{
 						Tuples: []xsql.Tuple{
-							{Emitter: "src1", Message: xsql.Message{ "id1" : 3, "f1" : "v1",},},
+							{Emitter: "src1", Message: xsql.Message{"id1": 3, "f1": "v1"}},
 						},
 					},
 				},
 				{
 					&xsql.JoinTuple{
 						Tuples: []xsql.Tuple{
-							{Emitter: "src1", Message: xsql.Message{ "id1" : 2, "f1" : "v2",},},
-							{Emitter: "src2", Message: xsql.Message{ "id2" : 4, "f2" : "w3",},},
+							{Emitter: "src1", Message: xsql.Message{"id1": 2, "f1": "v2"}},
+							{Emitter: "src2", Message: xsql.Message{"id2": 4, "f2": "w3"}},
 						},
 					},
 				},
 				{
 					&xsql.JoinTuple{
 						Tuples: []xsql.Tuple{
-							{Emitter: "src1", Message: xsql.Message{ "id1" : 1, "f1" : "v1",},},
-							{Emitter: "src2", Message: xsql.Message{ "id2" : 2, "f2" : "w2",},},
+							{Emitter: "src1", Message: xsql.Message{"id1": 1, "f1": "v1"}},
+							{Emitter: "src2", Message: xsql.Message{"id2": 2, "f2": "w2"}},
 						},
 					},
 				},
@@ -375,7 +375,7 @@ func TestOrderPlan_Apply(t *testing.T) {
 			break
 		}
 
-		pp := &OrderPlan{SortFields:stmt.SortFields}
+		pp := &OrderPlan{SortFields: stmt.SortFields}
 		result := pp.Apply(ctx, tt.data)
 		if !reflect.DeepEqual(tt.result, result) {
 			t.Errorf("%d. %q\n\nresult mismatch:\n\nexp=%#v\n\ngot=%#v\n\n", i, tt.sql, tt.result, result)

+ 83 - 83
xsql/plans/preprocessor.go

@@ -13,22 +13,22 @@ import (
 )
 
 type Preprocessor struct {
-	streamStmt  *xsql.StreamStmt
-	fields xsql.Fields
-	isEventTime bool
-	timestampField string
+	streamStmt      *xsql.StreamStmt
+	fields          xsql.Fields
+	isEventTime     bool
+	timestampField  string
 	timestampFormat string
 }
 
-func NewPreprocessor(s *xsql.StreamStmt, fs xsql.Fields, iet bool) (*Preprocessor, error){
+func NewPreprocessor(s *xsql.StreamStmt, fs xsql.Fields, iet bool) (*Preprocessor, error) {
 	p := &Preprocessor{streamStmt: s, fields: fs, isEventTime: iet}
 	if iet {
-		if tf, ok := s.Options["TIMESTAMP"]; ok{
+		if tf, ok := s.Options["TIMESTAMP"]; ok {
 			p.timestampField = tf
-		}else{
+		} else {
 			return nil, fmt.Errorf("preprocessor is set to be event time but stream option TIMESTAMP not found")
 		}
-		if ts, ok := s.Options["TIMESTAMP_FORMAT"]; ok{
+		if ts, ok := s.Options["TIMESTAMP_FORMAT"]; ok {
 			p.timestampFormat = ts
 		}
 	}
@@ -53,7 +53,7 @@ func (p *Preprocessor) Apply(ctx api.StreamContext, data interface{}) interface{
 	result := make(map[string]interface{})
 	for _, f := range p.streamStmt.StreamFields {
 		fname := strings.ToLower(f.Name)
-		if e := p.addRecField(f.FieldType, result, tuple.Message, fname); e != nil{
+		if e := p.addRecField(f.FieldType, result, tuple.Message, fname); e != nil {
 			log.Errorf("error in preprocessor: %s", e)
 			return nil
 		}
@@ -71,16 +71,16 @@ func (p *Preprocessor) Apply(ctx api.StreamContext, data interface{}) interface{
 	}
 
 	tuple.Message = result
-	if p.isEventTime{
-		if t, ok := result[p.timestampField]; ok{
-			if ts, err := common.InterfaceToUnixMilli(t, p.timestampFormat); err != nil{
+	if p.isEventTime {
+		if t, ok := result[p.timestampField]; ok {
+			if ts, err := common.InterfaceToUnixMilli(t, p.timestampFormat); err != nil {
 				log.Errorf("cannot convert timestamp field %s to timestamp with error %v", p.timestampField, err)
 				return nil
-			}else{
+			} else {
 				tuple.Timestamp = ts
 				log.Debugf("preprocessor calculate timstamp %d", tuple.Timestamp)
 			}
-		}else{
+		} else {
 			log.Errorf("cannot find timestamp field %s in tuple %v", p.timestampField, result)
 			return nil
 		}
@@ -88,10 +88,10 @@ func (p *Preprocessor) Apply(ctx api.StreamContext, data interface{}) interface{
 	return tuple
 }
 
-func (p *Preprocessor) parseTime(s string) (time.Time, error){
-	if f, ok := p.streamStmt.Options["TIMESTAMP_FORMAT"]; ok{
+func (p *Preprocessor) parseTime(s string) (time.Time, error) {
+	if f, ok := p.streamStmt.Options["TIMESTAMP_FORMAT"]; ok {
 		return common.ParseTime(s, f)
-	}else{
+	} else {
 		return time.Parse(common.JSISO, s)
 	}
 }
@@ -106,35 +106,35 @@ func (p *Preprocessor) addRecField(ft xsql.FieldType, r map[string]interface{},
 			case xsql.UNKNOWN:
 				return fmt.Errorf("invalid data type unknown defined for %s, please check the stream definition", t)
 			case xsql.BIGINT:
-				if jtype == reflect.Int{
+				if jtype == reflect.Int {
 					r[n] = t.(int)
-				}else if jtype == reflect.Float64{
+				} else if jtype == reflect.Float64 {
 					r[n] = int(t.(float64))
-				}else if jtype == reflect.String {
-					if i, err := strconv.Atoi(t.(string)); err != nil{
+				} else if jtype == reflect.String {
+					if i, err := strconv.Atoi(t.(string)); err != nil {
 						return fmt.Errorf("invalid data type for %s, expect bigint but found %s", n, t)
-					}else{
+					} else {
 						r[n] = i
 					}
-				}else{
+				} else {
 					return fmt.Errorf("invalid data type for %s, expect bigint but found %s", n, t)
 				}
 			case xsql.FLOAT:
-				if jtype == reflect.Float64{
+				if jtype == reflect.Float64 {
 					r[n] = t.(float64)
-				}else if jtype == reflect.String {
-					if f, err := strconv.ParseFloat(t.(string), 64); err != nil{
+				} else if jtype == reflect.String {
+					if f, err := strconv.ParseFloat(t.(string), 64); err != nil {
 						return fmt.Errorf("invalid data type for %s, expect float but found %s", n, t)
-					}else{
+					} else {
 						r[n] = f
 					}
-				}else{
+				} else {
 					return fmt.Errorf("invalid data type for %s, expect float but found %s", n, t)
 				}
 			case xsql.STRINGS:
-				if jtype == reflect.String{
+				if jtype == reflect.String {
 					r[n] = t.(string)
-				}else{
+				} else {
 					return fmt.Errorf("invalid data type for %s, expect string but found %s", n, t)
 				}
 			case xsql.DATETIME:
@@ -146,24 +146,24 @@ func (p *Preprocessor) addRecField(ft xsql.FieldType, r map[string]interface{},
 					ai := int64(t.(float64))
 					r[n] = common.TimeFromUnixMilli(ai)
 				case reflect.String:
-					if t, err := p.parseTime(t.(string)); err != nil{
+					if t, err := p.parseTime(t.(string)); err != nil {
 						return fmt.Errorf("invalid data type for %s, cannot convert to datetime: %s", n, err)
-					}else{
+					} else {
 						r[n] = t
 					}
 				default:
 					return fmt.Errorf("invalid data type for %s, expect datatime but find %v", n, t)
 				}
 			case xsql.BOOLEAN:
-				if jtype == reflect.Bool{
+				if jtype == reflect.Bool {
 					r[n] = t.(bool)
-				}else if jtype == reflect.String {
-					if i, err := strconv.ParseBool(t.(string)); err != nil{
+				} else if jtype == reflect.String {
+					if i, err := strconv.ParseBool(t.(string)); err != nil {
 						return fmt.Errorf("invalid data type for %s, expect boolean but found %s", n, t)
-					}else{
+					} else {
 						r[n] = i
 					}
-				}else{
+				} else {
 					return fmt.Errorf("invalid data type for %s, expect boolean but found %s", n, t)
 				}
 			default:
@@ -171,41 +171,41 @@ func (p *Preprocessor) addRecField(ft xsql.FieldType, r map[string]interface{},
 			}
 		case *xsql.ArrayType:
 			var s []interface{}
-			if jtype == reflect.Slice{
+			if jtype == reflect.Slice {
 				s = t.([]interface{})
-			}else if jtype == reflect.String {
+			} else if jtype == reflect.String {
 				err := json.Unmarshal([]byte(t.(string)), &s)
-				if err != nil{
+				if err != nil {
 					return fmt.Errorf("invalid data type for %s, expect array but found %s", n, t)
 				}
-			}else{
+			} else {
 				return fmt.Errorf("invalid data type for %s, expect array but found %s", n, t)
 			}
 
-			if tempArr, err := p.addArrayField(st, s); err !=nil{
+			if tempArr, err := p.addArrayField(st, s); err != nil {
 				return err
-			}else {
+			} else {
 				r[n] = tempArr
 			}
 		case *xsql.RecType:
 			nextJ := make(map[string]interface{})
-			if jtype == reflect.Map{
+			if jtype == reflect.Map {
 				nextJ, ok = t.(map[string]interface{})
 				if !ok {
 					return fmt.Errorf("invalid data type for %s, expect map but found %s", n, t)
 				}
-			}else if jtype == reflect.String {
+			} else if jtype == reflect.String {
 				err := json.Unmarshal([]byte(t.(string)), &nextJ)
-				if err != nil{
+				if err != nil {
 					return fmt.Errorf("invalid data type for %s, expect map but found %s", n, t)
 				}
-			}else{
+			} else {
 				return fmt.Errorf("invalid data type for %s, expect struct but found %s", n, t)
 			}
 			nextR := make(map[string]interface{})
 			for _, nextF := range st.StreamFields {
 				nextP := strings.ToLower(nextF.Name)
-				if e := p.addRecField(nextF.FieldType, nextR, nextJ, nextP); e != nil{
+				if e := p.addRecField(nextF.FieldType, nextR, nextJ, nextP); e != nil {
 					return e
 				}
 			}
@@ -214,7 +214,7 @@ func (p *Preprocessor) addRecField(ft xsql.FieldType, r map[string]interface{},
 			return fmt.Errorf("unsupported type %T", st)
 		}
 		return nil
-	}else{
+	} else {
 		return fmt.Errorf("invalid data %s, field %s not found", j, n)
 	}
 }
@@ -227,49 +227,49 @@ func (p *Preprocessor) addArrayField(ft *xsql.ArrayType, srcSlice []interface{})
 		case *xsql.ArrayType: //TODO handle array of array. Now the type is treated as interface{}
 			var tempSlice [][]interface{}
 			var s []interface{}
-			for i, t := range srcSlice{
+			for i, t := range srcSlice {
 				jtype := reflect.ValueOf(t).Kind()
-				if jtype == reflect.Slice || jtype == reflect.Array{
+				if jtype == reflect.Slice || jtype == reflect.Array {
 					s = t.([]interface{})
-				}else if jtype == reflect.String {
+				} else if jtype == reflect.String {
 					err := json.Unmarshal([]byte(t.(string)), &s)
-					if err != nil{
+					if err != nil {
 						return nil, fmt.Errorf("invalid data type for [%d], expect array but found %s", i, t)
 					}
-				}else{
+				} else {
 					return nil, fmt.Errorf("invalid data type for [%d], expect array but found %s", i, t)
 				}
-				if tempArr, err := p.addArrayField(st, s); err !=nil{
+				if tempArr, err := p.addArrayField(st, s); err != nil {
 					return nil, err
-				}else {
+				} else {
 					tempSlice = append(tempSlice, tempArr.([]interface{}))
 				}
 			}
 			return tempSlice, nil
 		case *xsql.RecType:
 			var tempSlice []map[string]interface{}
-			for i, t := range srcSlice{
+			for i, t := range srcSlice {
 				jtype := reflect.ValueOf(t).Kind()
 				j := make(map[string]interface{})
 				var ok bool
-				if jtype == reflect.Map{
+				if jtype == reflect.Map {
 					j, ok = t.(map[string]interface{})
 					if !ok {
 						return nil, fmt.Errorf("invalid data type for [%d], expect map but found %s", i, t)
 					}
 
-				}else if jtype == reflect.String {
+				} else if jtype == reflect.String {
 					err := json.Unmarshal([]byte(t.(string)), &j)
-					if err != nil{
+					if err != nil {
 						return nil, fmt.Errorf("invalid data type for [%d], expect map but found %s", i, t)
 					}
-				}else{
+				} else {
 					return nil, fmt.Errorf("invalid data type for [%d], expect map but found %s", i, t)
 				}
 				r := make(map[string]interface{})
 				for _, f := range st.StreamFields {
 					n := f.Name
-					if e := p.addRecField(f.FieldType, r, j, n); e != nil{
+					if e := p.addRecField(f.FieldType, r, j, n); e != nil {
 						return nil, e
 					}
 				}
@@ -279,7 +279,7 @@ func (p *Preprocessor) addArrayField(ft *xsql.ArrayType, srcSlice []interface{})
 		default:
 			return nil, fmt.Errorf("unsupported type %T", st)
 		}
-	}else{ //basic type
+	} else { //basic type
 		switch ft.Type {
 		case xsql.UNKNOWN:
 			return nil, fmt.Errorf("invalid data type unknown defined for %s, please checke the stream definition", srcSlice)
@@ -287,15 +287,15 @@ func (p *Preprocessor) addArrayField(ft *xsql.ArrayType, srcSlice []interface{})
 			var tempSlice []int
 			for i, t := range srcSlice {
 				jtype := reflect.ValueOf(t).Kind()
-				if jtype == reflect.Float64{
+				if jtype == reflect.Float64 {
 					tempSlice = append(tempSlice, int(t.(float64)))
-				}else if jtype == reflect.String {
-					if v, err := strconv.Atoi(t.(string)); err != nil{
+				} else if jtype == reflect.String {
+					if v, err := strconv.Atoi(t.(string)); err != nil {
 						return nil, fmt.Errorf("invalid data type for [%d], expect float but found %s", i, t)
-					}else{
+					} else {
 						tempSlice = append(tempSlice, v)
 					}
-				}else{
+				} else {
 					return nil, fmt.Errorf("invalid data type for [%d], expect float but found %s", i, t)
 				}
 			}
@@ -304,15 +304,15 @@ func (p *Preprocessor) addArrayField(ft *xsql.ArrayType, srcSlice []interface{})
 			var tempSlice []float64
 			for i, t := range srcSlice {
 				jtype := reflect.ValueOf(t).Kind()
-				if jtype == reflect.Float64{
+				if jtype == reflect.Float64 {
 					tempSlice = append(tempSlice, t.(float64))
-				}else if jtype == reflect.String {
-					if f, err := strconv.ParseFloat(t.(string), 64); err != nil{
+				} else if jtype == reflect.String {
+					if f, err := strconv.ParseFloat(t.(string), 64); err != nil {
 						return nil, fmt.Errorf("invalid data type for [%d], expect float but found %s", i, t)
-					}else{
+					} else {
 						tempSlice = append(tempSlice, f)
 					}
-				}else{
+				} else {
 					return nil, fmt.Errorf("invalid data type for [%d], expect float but found %s", i, t)
 				}
 			}
@@ -320,9 +320,9 @@ func (p *Preprocessor) addArrayField(ft *xsql.ArrayType, srcSlice []interface{})
 		case xsql.STRINGS:
 			var tempSlice []string
 			for i, t := range srcSlice {
-				if reflect.ValueOf(t).Kind() == reflect.String{
+				if reflect.ValueOf(t).Kind() == reflect.String {
 					tempSlice = append(tempSlice, t.(string))
-				}else{
+				} else {
 					return nil, fmt.Errorf("invalid data type for [%d], expect string but found %s", i, t)
 				}
 			}
@@ -339,9 +339,9 @@ func (p *Preprocessor) addArrayField(ft *xsql.ArrayType, srcSlice []interface{})
 					ai := int64(t.(float64))
 					tempSlice = append(tempSlice, common.TimeFromUnixMilli(ai))
 				case reflect.String:
-					if ai, err := p.parseTime(t.(string)); err != nil{
+					if ai, err := p.parseTime(t.(string)); err != nil {
 						return nil, fmt.Errorf("invalid data type for %s, cannot convert to datetime: %s", t, err)
-					}else{
+					} else {
 						tempSlice = append(tempSlice, ai)
 					}
 				default:
@@ -353,15 +353,15 @@ func (p *Preprocessor) addArrayField(ft *xsql.ArrayType, srcSlice []interface{})
 			var tempSlice []bool
 			for i, t := range srcSlice {
 				jtype := reflect.ValueOf(t).Kind()
-				if jtype == reflect.Bool{
+				if jtype == reflect.Bool {
 					tempSlice = append(tempSlice, t.(bool))
-				}else if jtype == reflect.String {
-					if v, err := strconv.ParseBool(t.(string)); err != nil{
+				} else if jtype == reflect.String {
+					if v, err := strconv.ParseBool(t.(string)); err != nil {
 						return nil, fmt.Errorf("invalid data type for [%d], expect boolean but found %s", i, t)
-					}else{
+					} else {
 						tempSlice = append(tempSlice, v)
 					}
-				}else{
+				} else {
 					return nil, fmt.Errorf("invalid data type for [%d], expect boolean but found %s", i, t)
 				}
 			}
@@ -370,4 +370,4 @@ func (p *Preprocessor) addArrayField(ft *xsql.ArrayType, srcSlice []interface{})
 			return nil, fmt.Errorf("invalid data type for %T, datetime type is not supported yet", ft.Type)
 		}
 	}
-}
+}

+ 62 - 62
xsql/plans/preprocessor_test.go

@@ -15,8 +15,8 @@ import (
 func TestPreprocessor_Apply(t *testing.T) {
 
 	var tests = []struct {
-		stmt *xsql.StreamStmt
-		data []byte
+		stmt   *xsql.StreamStmt
+		data   []byte
 		result interface{}
 	}{
 		//Basic type
@@ -27,7 +27,7 @@ func TestPreprocessor_Apply(t *testing.T) {
 					{Name: "abc", FieldType: &xsql.BasicType{Type: xsql.BIGINT}},
 				},
 			},
-			data: []byte(`{"a": 6}`),
+			data:   []byte(`{"a": 6}`),
 			result: nil,
 		},
 
@@ -40,8 +40,8 @@ func TestPreprocessor_Apply(t *testing.T) {
 			},
 			data: []byte(`{"abc": 6}`),
 			result: &xsql.Tuple{Message: xsql.Message{
-					"abc": int(6),
-				},
+				"abc": int(6),
+			},
 			},
 		},
 		{
@@ -56,7 +56,7 @@ func TestPreprocessor_Apply(t *testing.T) {
 			result: &xsql.Tuple{Message: xsql.Message{
 				"abc": float64(34),
 				"def": "hello",
-				},
+			},
 			},
 		},
 		{
@@ -82,7 +82,7 @@ func TestPreprocessor_Apply(t *testing.T) {
 					{Name: "def", FieldType: &xsql.BasicType{Type: xsql.BOOLEAN}},
 				},
 			},
-			data: []byte(`{"abc": 77, "def" : "hello"}`),
+			data:   []byte(`{"abc": 77, "def" : "hello"}`),
 			result: nil,
 		},
 		{
@@ -93,7 +93,7 @@ func TestPreprocessor_Apply(t *testing.T) {
 					{Name: "def", FieldType: &xsql.BasicType{Type: xsql.BOOLEAN}},
 				},
 			},
-			data: []byte(`{"a": {"b" : "hello"}}`),
+			data:   []byte(`{"a": {"b" : "hello"}}`),
 			result: nil,
 		},
 		//Rec type
@@ -110,7 +110,7 @@ func TestPreprocessor_Apply(t *testing.T) {
 			},
 			data: []byte(`{"a": {"b" : "hello"}}`),
 			result: &xsql.Tuple{Message: xsql.Message{
-					"a": map[string]interface{}{
+				"a": map[string]interface{}{
 					"b": "hello",
 				},
 			},
@@ -153,12 +153,12 @@ func TestPreprocessor_Apply(t *testing.T) {
 			},
 			data: []byte(`{"a": [{"b" : "hello1"}, {"b" : "hello2"}]}`),
 			result: &xsql.Tuple{Message: xsql.Message{
-					"a": []map[string]interface{}{
-						{"b": "hello1"},
-						{"b": "hello2"},
-					},
+				"a": []map[string]interface{}{
+					{"b": "hello1"},
+					{"b": "hello2"},
 				},
 			},
+			},
 		},
 		{
 			stmt: &xsql.StreamStmt{
@@ -197,14 +197,14 @@ func TestPreprocessor_Apply(t *testing.T) {
 			},
 			data: []byte(`{"a": {"b" : "hello", "c": {"d": 35.2}}}`),
 			result: &xsql.Tuple{Message: xsql.Message{
-					"a": map[string]interface{}{
-						"b": "hello",
-						"c": map[string]interface{}{
-							"d": int(35),
-						},
+				"a": map[string]interface{}{
+					"b": "hello",
+					"c": map[string]interface{}{
+						"d": int(35),
 					},
 				},
 			},
+			},
 		},
 	}
 
@@ -222,7 +222,7 @@ func TestPreprocessor_Apply(t *testing.T) {
 			log.Fatal(e)
 			return
 		} else {
-			tuple := &xsql.Tuple{Message:dm}
+			tuple := &xsql.Tuple{Message: dm}
 			result := pp.Apply(ctx, tuple)
 			if !reflect.DeepEqual(tt.result, result) {
 				t.Errorf("%d. %q\n\nresult mismatch:\n\nexp=%#v\n\ngot=%#v\n\n", i, tuple, tt.result, result)
@@ -232,10 +232,10 @@ func TestPreprocessor_Apply(t *testing.T) {
 	}
 }
 
-func TestPreprocessorTime_Apply(t *testing.T){
+func TestPreprocessorTime_Apply(t *testing.T) {
 	var tests = []struct {
-		stmt *xsql.StreamStmt
-		data []byte
+		stmt   *xsql.StreamStmt
+		data   []byte
 		result interface{}
 	}{
 		{
@@ -261,7 +261,7 @@ func TestPreprocessorTime_Apply(t *testing.T){
 					{Name: "def", FieldType: &xsql.BasicType{Type: xsql.DATETIME}},
 				},
 			},
-			data: []byte(`{"abc": "2019-09-19T00:55:1dd5Z", "def" : 111568854573431}`),
+			data:   []byte(`{"abc": "2019-09-19T00:55:1dd5Z", "def" : 111568854573431}`),
 			result: nil,
 		},
 		{
@@ -272,13 +272,13 @@ func TestPreprocessorTime_Apply(t *testing.T){
 					{Name: "def", FieldType: &xsql.BasicType{Type: xsql.DATETIME}},
 				},
 				Options: map[string]string{
-					"DATASOURCE" : "users",
-					"FORMAT" : "AVRO",
-					"KEY" : "USERID",
-					"CONF_KEY" : "srv1",
-					"TYPE" : "MQTT",
-					"TIMESTAMP" : "USERID",
-					"TIMESTAMP_FORMAT" : "yyyy-MM-dd 'at' HH:mm:ss'Z'X",
+					"DATASOURCE":       "users",
+					"FORMAT":           "AVRO",
+					"KEY":              "USERID",
+					"CONF_KEY":         "srv1",
+					"TYPE":             "MQTT",
+					"TIMESTAMP":        "USERID",
+					"TIMESTAMP_FORMAT": "yyyy-MM-dd 'at' HH:mm:ss'Z'X",
 				},
 			},
 			data: []byte(`{"abc": "2019-09-19 at 18:55:15Z+07", "def" : 1568854573431}`),
@@ -343,11 +343,11 @@ func TestPreprocessorTime_Apply(t *testing.T){
 			log.Fatal(e)
 			return
 		} else {
-			tuple := &xsql.Tuple{Message:dm}
+			tuple := &xsql.Tuple{Message: dm}
 			result := pp.Apply(ctx, tuple)
 			//workaround make sure all the timezone are the same for time vars or the DeepEqual will be false.
-			if rt, ok := result.(*xsql.Tuple); ok{
-				if rtt, ok := rt.Message["abc"].(time.Time); ok{
+			if rt, ok := result.(*xsql.Tuple); ok {
+				if rtt, ok := rt.Message["abc"].(time.Time); ok {
 					rt.Message["abc"] = rtt.UTC()
 				}
 			}
@@ -362,8 +362,8 @@ func TestPreprocessorTime_Apply(t *testing.T){
 func TestPreprocessorEventtime_Apply(t *testing.T) {
 
 	var tests = []struct {
-		stmt *xsql.StreamStmt
-		data []byte
+		stmt   *xsql.StreamStmt
+		data   []byte
 		result interface{}
 	}{
 		//Basic type
@@ -374,13 +374,13 @@ func TestPreprocessorEventtime_Apply(t *testing.T) {
 					{Name: "abc", FieldType: &xsql.BasicType{Type: xsql.BIGINT}},
 				},
 				Options: map[string]string{
-					"DATASOURCE" : "users",
-					"FORMAT" : "AVRO",
-					"KEY" : "USERID",
-					"CONF_KEY" : "srv1",
-					"TYPE" : "MQTT",
-					"TIMESTAMP" : "abc",
-					"TIMESTAMP_FORMAT" : "yyyy-MM-dd''T''HH:mm:ssX'",
+					"DATASOURCE":       "users",
+					"FORMAT":           "AVRO",
+					"KEY":              "USERID",
+					"CONF_KEY":         "srv1",
+					"TYPE":             "MQTT",
+					"TIMESTAMP":        "abc",
+					"TIMESTAMP_FORMAT": "yyyy-MM-dd''T''HH:mm:ssX'",
 				},
 			},
 			data: []byte(`{"abc": 1568854515000}`),
@@ -396,11 +396,11 @@ func TestPreprocessorEventtime_Apply(t *testing.T) {
 					{Name: "abc", FieldType: &xsql.BasicType{Type: xsql.BOOLEAN}},
 				},
 				Options: map[string]string{
-					"DATASOURCE" : "users",
-					"TIMESTAMP" : "abc",
+					"DATASOURCE": "users",
+					"TIMESTAMP":  "abc",
 				},
 			},
-			data: []byte(`{"abc": true}`),
+			data:   []byte(`{"abc": true}`),
 			result: nil,
 		},
 		{
@@ -411,8 +411,8 @@ func TestPreprocessorEventtime_Apply(t *testing.T) {
 					{Name: "def", FieldType: &xsql.BasicType{Type: xsql.STRINGS}},
 				},
 				Options: map[string]string{
-					"DATASOURCE" : "users",
-					"TIMESTAMP" : "def",
+					"DATASOURCE": "users",
+					"TIMESTAMP":  "def",
 				},
 			},
 			data: []byte(`{"abc": 34, "def" : "2019-09-23T02:47:29.754Z", "ghi": 50}`),
@@ -430,8 +430,8 @@ func TestPreprocessorEventtime_Apply(t *testing.T) {
 					{Name: "def", FieldType: &xsql.BasicType{Type: xsql.DATETIME}},
 				},
 				Options: map[string]string{
-					"DATASOURCE" : "users",
-					"TIMESTAMP" : "abc",
+					"DATASOURCE": "users",
+					"TIMESTAMP":  "abc",
 				},
 			},
 			data: []byte(`{"abc": "2019-09-19T00:55:15.000Z", "def" : 1568854573431}`),
@@ -449,9 +449,9 @@ func TestPreprocessorEventtime_Apply(t *testing.T) {
 					{Name: "def", FieldType: &xsql.BasicType{Type: xsql.STRINGS}},
 				},
 				Options: map[string]string{
-					"DATASOURCE" : "users",
-					"TIMESTAMP" : "def",
-					"TIMESTAMP_FORMAT" : "yyyy-MM-dd'AT'HH:mm:ss",
+					"DATASOURCE":       "users",
+					"TIMESTAMP":        "def",
+					"TIMESTAMP_FORMAT": "yyyy-MM-dd'AT'HH:mm:ss",
 				},
 			},
 			data: []byte(`{"abc": 34, "def" : "2019-09-23AT02:47:29", "ghi": 50}`),
@@ -469,12 +469,12 @@ func TestPreprocessorEventtime_Apply(t *testing.T) {
 					{Name: "def", FieldType: &xsql.BasicType{Type: xsql.STRINGS}},
 				},
 				Options: map[string]string{
-					"DATASOURCE" : "users",
-					"TIMESTAMP" : "def",
-					"TIMESTAMP_FORMAT" : "yyyy-MM-ddaHH:mm:ss",
+					"DATASOURCE":       "users",
+					"TIMESTAMP":        "def",
+					"TIMESTAMP_FORMAT": "yyyy-MM-ddaHH:mm:ss",
 				},
 			},
-			data: []byte(`{"abc": 34, "def" : "2019-09-23AT02:47:29", "ghi": 50}`),
+			data:   []byte(`{"abc": 34, "def" : "2019-09-23AT02:47:29", "ghi": 50}`),
 			result: nil,
 		},
 	}
@@ -486,8 +486,8 @@ func TestPreprocessorEventtime_Apply(t *testing.T) {
 	ctx := contexts.WithValue(contexts.Background(), contexts.LoggerKey, contextLogger)
 	for i, tt := range tests {
 
-		pp, err := NewPreprocessor(tt.stmt, nil,true)
-		if err != nil{
+		pp, err := NewPreprocessor(tt.stmt, nil, true)
+		if err != nil {
 			t.Error(err)
 		}
 
@@ -496,11 +496,11 @@ func TestPreprocessorEventtime_Apply(t *testing.T) {
 			log.Fatal(e)
 			return
 		} else {
-			tuple := &xsql.Tuple{Message:dm}
+			tuple := &xsql.Tuple{Message: dm}
 			result := pp.Apply(ctx, tuple)
 			//workaround make sure all the timezone are the same for time vars or the DeepEqual will be false.
-			if rt, ok := result.(*xsql.Tuple); ok{
-				if rtt, ok := rt.Message["abc"].(time.Time); ok{
+			if rt, ok := result.(*xsql.Tuple); ok {
+				if rtt, ok := rt.Message["abc"].(time.Time); ok {
 					rt.Message["abc"] = rtt.UTC()
 				}
 			}
@@ -510,4 +510,4 @@ func TestPreprocessorEventtime_Apply(t *testing.T) {
 		}
 
 	}
-}
+}

+ 18 - 19
xsql/plans/project_operator.go

@@ -10,7 +10,7 @@ import (
 )
 
 type ProjectPlan struct {
-	Fields xsql.Fields
+	Fields      xsql.Fields
 	IsAggregate bool
 
 	isTest bool
@@ -37,7 +37,7 @@ func (pp *ProjectPlan) Apply(ctx api.StreamContext, data interface{}) interface{
 		for _, v := range ms {
 			ve := pp.getVE(&v, input)
 			results = append(results, project(pp.Fields, ve, pp.isTest))
-			if pp.IsAggregate{
+			if pp.IsAggregate {
 				break
 			}
 		}
@@ -46,12 +46,12 @@ func (pp *ProjectPlan) Apply(ctx api.StreamContext, data interface{}) interface{
 		for _, v := range ms {
 			ve := pp.getVE(&v, input)
 			results = append(results, project(pp.Fields, ve, pp.isTest))
-			if pp.IsAggregate{
+			if pp.IsAggregate {
 				break
 			}
 		}
 	case xsql.GroupedTuplesSet:
-		for _, v := range input{
+		for _, v := range input {
 			ve := pp.getVE(v[0], v)
 			results = append(results, project(pp.Fields, ve, pp.isTest))
 		}
@@ -68,10 +68,10 @@ func (pp *ProjectPlan) Apply(ctx api.StreamContext, data interface{}) interface{
 	}
 }
 
-func (pp *ProjectPlan) getVE(tuple xsql.DataValuer, agg xsql.AggregateData) *xsql.ValuerEval{
-	if pp.IsAggregate{
+func (pp *ProjectPlan) getVE(tuple xsql.DataValuer, agg xsql.AggregateData) *xsql.ValuerEval {
+	if pp.IsAggregate {
 		return &xsql.ValuerEval{Valuer: xsql.MultiAggregateValuer(agg, tuple, &xsql.FunctionValuer{}, &xsql.AggregateFunctionValuer{Data: agg}, &xsql.WildcardValuer{Data: tuple})}
-	}else{
+	} else {
 		return &xsql.ValuerEval{Valuer: xsql.MultiValuer(tuple, &xsql.FunctionValuer{}, &xsql.WildcardValuer{Data: tuple})}
 	}
 }
@@ -80,23 +80,23 @@ func project(fs xsql.Fields, ve *xsql.ValuerEval, isTest bool) map[string]interf
 	result := make(map[string]interface{})
 	for _, f := range fs {
 		//Avoid to re-evaluate for non-agg field has alias name, which was already evaluated in pre-processor operator.
-		if f.AName != "" && (!xsql.HasAggFuncs(f.Expr)) && !isTest{
-			fr := &xsql.FieldRef{StreamName:"", Name:f.AName}
+		if f.AName != "" && (!xsql.HasAggFuncs(f.Expr)) && !isTest {
+			fr := &xsql.FieldRef{StreamName: "", Name: f.AName}
 			v := ve.Eval(fr)
 			result[f.AName] = v
 		} else {
 			v := ve.Eval(f.Expr)
-			if _, ok := f.Expr.(*xsql.Wildcard); ok || f.Name == "*"{
+			if _, ok := f.Expr.(*xsql.Wildcard); ok || f.Name == "*" {
 				switch val := v.(type) {
-				case map[string]interface{} :
-					for k, v := range val{
-						if _, ok := result[k]; !ok{
+				case map[string]interface{}:
+					for k, v := range val {
+						if _, ok := result[k]; !ok {
 							result[k] = v
 						}
 					}
 				case xsql.Message:
-					for k, v := range val{
-						if _, ok := result[k]; !ok{
+					for k, v := range val {
+						if _, ok := result[k]; !ok {
 							result[k] = v
 						}
 					}
@@ -106,7 +106,7 @@ func project(fs xsql.Fields, ve *xsql.ValuerEval, isTest bool) map[string]interf
 			} else {
 				if v != nil {
 					n := assignName(f.Name, f.AName, result)
-					if _, ok := result[n]; !ok{
+					if _, ok := result[n]; !ok {
 						result[n] = v
 					}
 				}
@@ -116,10 +116,9 @@ func project(fs xsql.Fields, ve *xsql.ValuerEval, isTest bool) map[string]interf
 	return result
 }
 
-
 const DEFAULT_FIELD_NAME_PREFIX string = "rengine_field_"
 
-func assignName(name, alias string, fields map[string] interface{}) string {
+func assignName(name, alias string, fields map[string]interface{}) string {
 	if result := strings.Trim(alias, " "); result != "" {
 		return result
 	}
@@ -136,4 +135,4 @@ func assignName(name, alias string, fields map[string] interface{}) string {
 	}
 	fmt.Printf("Cannot assign a default field name")
 	return ""
-}
+}

文件差异内容过多而无法显示
+ 264 - 275
xsql/plans/project_test.go


+ 82 - 82
xsql/plans/str_func_test.go

@@ -13,8 +13,8 @@ import (
 
 func TestStrFunc_Apply1(t *testing.T) {
 	var tests = []struct {
-		sql  string
-		data *xsql.Tuple
+		sql    string
+		data   *xsql.Tuple
 		result []map[string]interface{}
 	}{
 		{
@@ -22,9 +22,9 @@ func TestStrFunc_Apply1(t *testing.T) {
 			data: &xsql.Tuple{
 				Emitter: "test",
 				Message: xsql.Message{
-					"a" : "mya",
-					"b" : "myb",
-					"c" : "myc",
+					"a": "mya",
+					"b": "myb",
+					"c": "myc",
 				},
 			},
 			result: []map[string]interface{}{{
@@ -37,9 +37,9 @@ func TestStrFunc_Apply1(t *testing.T) {
 			data: &xsql.Tuple{
 				Emitter: "test",
 				Message: xsql.Message{
-					"a" : "mya",
-					"b" : "myb",
-					"c" : "myc",
+					"a": "mya",
+					"b": "myb",
+					"c": "myc",
 				},
 			},
 			result: []map[string]interface{}{{
@@ -51,9 +51,9 @@ func TestStrFunc_Apply1(t *testing.T) {
 			data: &xsql.Tuple{
 				Emitter: "test",
 				Message: xsql.Message{
-					"a" : "mya",
-					"b" : "ya",
-					"c" : "myc",
+					"a": "mya",
+					"b": "ya",
+					"c": "myc",
 				},
 			},
 			result: []map[string]interface{}{{
@@ -65,9 +65,9 @@ func TestStrFunc_Apply1(t *testing.T) {
 			data: &xsql.Tuple{
 				Emitter: "test",
 				Message: xsql.Message{
-					"a" : common.TimeFromUnixMilli(1568854515000),
-					"b" : "ya",
-					"c" : "myc",
+					"a": common.TimeFromUnixMilli(1568854515000),
+					"b": "ya",
+					"c": "myc",
 				},
 			},
 			result: []map[string]interface{}{{
@@ -79,9 +79,9 @@ func TestStrFunc_Apply1(t *testing.T) {
 			data: &xsql.Tuple{
 				Emitter: "test",
 				Message: xsql.Message{
-					"a" : "mya",
-					"b" : "ya",
-					"c" : "myc",
+					"a": "mya",
+					"b": "ya",
+					"c": "myc",
 				},
 			},
 			result: []map[string]interface{}{{
@@ -93,9 +93,9 @@ func TestStrFunc_Apply1(t *testing.T) {
 			data: &xsql.Tuple{
 				Emitter: "test",
 				Message: xsql.Message{
-					"a" : "中国",
-					"b" : "ya",
-					"c" : "myc",
+					"a": "中国",
+					"b": "ya",
+					"c": "myc",
 				},
 			},
 			result: []map[string]interface{}{{
@@ -107,9 +107,9 @@ func TestStrFunc_Apply1(t *testing.T) {
 			data: &xsql.Tuple{
 				Emitter: "test",
 				Message: xsql.Message{
-					"a" : "中国",
-					"b" : "ya",
-					"c" : "myc",
+					"a": "中国",
+					"b": "ya",
+					"c": "myc",
 				},
 			},
 			result: []map[string]interface{}{{
@@ -121,9 +121,9 @@ func TestStrFunc_Apply1(t *testing.T) {
 			data: &xsql.Tuple{
 				Emitter: "test",
 				Message: xsql.Message{
-					"a" : "NYCNicks",
-					"b" : "ya",
-					"c" : "myc",
+					"a": "NYCNicks",
+					"b": "ya",
+					"c": "myc",
 				},
 			},
 			result: []map[string]interface{}{{
@@ -135,9 +135,9 @@ func TestStrFunc_Apply1(t *testing.T) {
 			data: &xsql.Tuple{
 				Emitter: "test",
 				Message: xsql.Message{
-					"a" : "NYCNicks",
-					"b" : "ya",
-					"c" : "myc",
+					"a": "NYCNicks",
+					"b": "ya",
+					"c": "myc",
 				},
 			},
 			result: []map[string]interface{}{{
@@ -149,9 +149,9 @@ func TestStrFunc_Apply1(t *testing.T) {
 			data: &xsql.Tuple{
 				Emitter: "test",
 				Message: xsql.Message{
-					"a" : " \ttrimme\n ",
-					"b" : "ya",
-					"c" : "myc",
+					"a": " \ttrimme\n ",
+					"b": "ya",
+					"c": "myc",
 				},
 			},
 			result: []map[string]interface{}{{
@@ -163,9 +163,9 @@ func TestStrFunc_Apply1(t *testing.T) {
 			data: &xsql.Tuple{
 				Emitter: "test",
 				Message: xsql.Message{
-					"a" : "中国",
-					"b" : "ya",
-					"c" : "myc",
+					"a": "中国",
+					"b": "ya",
+					"c": "myc",
 				},
 			},
 			result: []map[string]interface{}{{
@@ -177,9 +177,9 @@ func TestStrFunc_Apply1(t *testing.T) {
 			data: &xsql.Tuple{
 				Emitter: "test",
 				Message: xsql.Message{
-					"a" : "中国",
-					"b" : "ya",
-					"c" : "myc",
+					"a": "中国",
+					"b": "ya",
+					"c": "myc",
 				},
 			},
 			result: []map[string]interface{}{{
@@ -191,9 +191,9 @@ func TestStrFunc_Apply1(t *testing.T) {
 			data: &xsql.Tuple{
 				Emitter: "test",
 				Message: xsql.Message{
-					"a" : "seafood",
-					"b" : "ya",
-					"c" : "myc",
+					"a": "seafood",
+					"b": "ya",
+					"c": "myc",
 				},
 			},
 			result: []map[string]interface{}{{
@@ -205,9 +205,9 @@ func TestStrFunc_Apply1(t *testing.T) {
 			data: &xsql.Tuple{
 				Emitter: "test",
 				Message: xsql.Message{
-					"a" : "seafood",
-					"b" : "ya",
-					"c" : "myc",
+					"a": "seafood",
+					"b": "ya",
+					"c": "myc",
 				},
 			},
 			result: []map[string]interface{}{{
@@ -219,9 +219,9 @@ func TestStrFunc_Apply1(t *testing.T) {
 			data: &xsql.Tuple{
 				Emitter: "test",
 				Message: xsql.Message{
-					"a" : "-ab-axxb-",
-					"b" : "ya",
-					"c" : "myc",
+					"a": "-ab-axxb-",
+					"b": "ya",
+					"c": "myc",
 				},
 			},
 			result: []map[string]interface{}{{
@@ -233,9 +233,9 @@ func TestStrFunc_Apply1(t *testing.T) {
 			data: &xsql.Tuple{
 				Emitter: "test",
 				Message: xsql.Message{
-					"a" : "seafood",
-					"b" : "ya",
-					"c" : "myc",
+					"a": "seafood",
+					"b": "ya",
+					"c": "myc",
 				},
 			},
 			result: []map[string]interface{}{{
@@ -247,9 +247,9 @@ func TestStrFunc_Apply1(t *testing.T) {
 			data: &xsql.Tuple{
 				Emitter: "test",
 				Message: xsql.Message{
-					"a" : "NYCNicks",
-					"b" : "ya",
-					"c" : "myc",
+					"a": "NYCNicks",
+					"b": "ya",
+					"c": "myc",
 				},
 			},
 			result: []map[string]interface{}{{
@@ -261,9 +261,9 @@ func TestStrFunc_Apply1(t *testing.T) {
 			data: &xsql.Tuple{
 				Emitter: "test",
 				Message: xsql.Message{
-					"a" : " \ttrimme\n ",
-					"b" : "ya",
-					"c" : "myc",
+					"a": " \ttrimme\n ",
+					"b": "ya",
+					"c": "myc",
 				},
 			},
 			result: []map[string]interface{}{{
@@ -275,9 +275,9 @@ func TestStrFunc_Apply1(t *testing.T) {
 			data: &xsql.Tuple{
 				Emitter: "test",
 				Message: xsql.Message{
-					"a" : "NYCNicks",
-					"b" : "ya",
-					"c" : "myc",
+					"a": "NYCNicks",
+					"b": "ya",
+					"c": "myc",
 				},
 			},
 			result: []map[string]interface{}{{
@@ -289,9 +289,9 @@ func TestStrFunc_Apply1(t *testing.T) {
 			data: &xsql.Tuple{
 				Emitter: "test",
 				Message: xsql.Message{
-					"a" : "NYCNicks",
-					"b" : "ya",
-					"c" : "myc",
+					"a": "NYCNicks",
+					"b": "ya",
+					"c": "myc",
 				},
 			},
 			result: []map[string]interface{}{{
@@ -303,9 +303,9 @@ func TestStrFunc_Apply1(t *testing.T) {
 			data: &xsql.Tuple{
 				Emitter: "test",
 				Message: xsql.Message{
-					"a" : "mya",
-					"b" : "ya",
-					"c" : "myc",
+					"a": "mya",
+					"b": "ya",
+					"c": "myc",
 				},
 			},
 			result: []map[string]interface{}{{
@@ -317,9 +317,9 @@ func TestStrFunc_Apply1(t *testing.T) {
 			data: &xsql.Tuple{
 				Emitter: "test",
 				Message: xsql.Message{
-					"a" : "mya",
-					"b" : "ya",
-					"c" : "myc",
+					"a": "mya",
+					"b": "ya",
+					"c": "myc",
 				},
 			},
 			result: []map[string]interface{}{{
@@ -331,9 +331,9 @@ func TestStrFunc_Apply1(t *testing.T) {
 			data: &xsql.Tuple{
 				Emitter: "test",
 				Message: xsql.Message{
-					"a" : " \ttrimme\n ",
-					"b" : "ya",
-					"c" : "myc",
+					"a": " \ttrimme\n ",
+					"b": "ya",
+					"c": "myc",
 				},
 			},
 			result: []map[string]interface{}{{
@@ -345,9 +345,9 @@ func TestStrFunc_Apply1(t *testing.T) {
 			data: &xsql.Tuple{
 				Emitter: "test",
 				Message: xsql.Message{
-					"a" : "NYCNicks",
-					"b" : "ya",
-					"c" : "myc",
+					"a": "NYCNicks",
+					"b": "ya",
+					"c": "myc",
 				},
 			},
 			result: []map[string]interface{}{{
@@ -360,7 +360,7 @@ func TestStrFunc_Apply1(t *testing.T) {
 			data: &xsql.Tuple{
 				Emitter: "test",
 				Message: xsql.Message{
-					"a" : "test/device001/message",
+					"a": "test/device001/message",
 				},
 			},
 			result: []map[string]interface{}{{
@@ -373,7 +373,7 @@ func TestStrFunc_Apply1(t *testing.T) {
 			data: &xsql.Tuple{
 				Emitter: "test",
 				Message: xsql.Message{
-					"a" : "test/device001/message",
+					"a": "test/device001/message",
 				},
 			},
 			result: []map[string]interface{}{{
@@ -386,7 +386,7 @@ func TestStrFunc_Apply1(t *testing.T) {
 			data: &xsql.Tuple{
 				Emitter: "test",
 				Message: xsql.Message{
-					"a" : "test/device001/message",
+					"a": "test/device001/message",
 				},
 			},
 			result: []map[string]interface{}{{
@@ -399,7 +399,7 @@ func TestStrFunc_Apply1(t *testing.T) {
 			data: &xsql.Tuple{
 				Emitter: "test",
 				Message: xsql.Message{
-					"a" : "/test/device001/message",
+					"a": "/test/device001/message",
 				},
 			},
 			result: []map[string]interface{}{{
@@ -413,10 +413,10 @@ func TestStrFunc_Apply1(t *testing.T) {
 			data: &xsql.Tuple{
 				Emitter: "test",
 				Message: xsql.Message{
-					"a" : "test/device001/message",
+					"a": "test/device001/message",
 				},
 			},
-			result: []map[string]interface{}{map[string]interface {}{}},
+			result: []map[string]interface{}{map[string]interface{}{}},
 		},
 	}
 
@@ -428,7 +428,7 @@ func TestStrFunc_Apply1(t *testing.T) {
 		if err != nil || stmt == nil {
 			t.Errorf("parse sql %s error %v", tt.sql, err)
 		}
-		pp := &ProjectPlan{Fields:stmt.Fields}
+		pp := &ProjectPlan{Fields: stmt.Fields}
 		pp.isTest = true
 		result := pp.Apply(ctx, tt.data)
 		var mapRes []map[string]interface{}
@@ -447,4 +447,4 @@ func TestStrFunc_Apply1(t *testing.T) {
 			t.Errorf("The returned result is not type of []byte\n")
 		}
 	}
-}
+}

+ 17 - 17
xsql/processors/extension_test.go

@@ -24,12 +24,13 @@ func setup() *RuleProcessor {
 	}
 	log.Infof("db location is %s", dbDir)
 
+	p := NewStreamProcessor(path.Join(dbDir, "stream"))
 	demo := `DROP STREAM ext`
-	NewStreamProcessor(demo, path.Join(dbDir, "stream")).Exec()
-	demo = "CREATE STREAM ext (count bigint) WITH (DATASOURCE=\"users\", FORMAT=\"JSON\", TYPE=\"random\", CONF_KEY=\"ext\")"
+	p.ExecStmt(demo)
 
-	_, err = NewStreamProcessor(demo, path.Join(dbDir, "stream")).Exec()
-	if err != nil{
+	demo = "CREATE STREAM ext (count bigint) WITH (DATASOURCE=\"users\", FORMAT=\"JSON\", TYPE=\"random\", CONF_KEY=\"ext\")"
+	_, err = p.ExecStmt(demo)
+	if err != nil {
 		panic(err)
 	}
 	rp := NewRuleProcessor(dbDir)
@@ -43,14 +44,13 @@ var CACHE_FILE = "cache"
 func TestExtensions(t *testing.T) {
 	log := common.Log
 	var tests = []struct {
-		name    string
-		rj	string
+		name string
+		rj   string
 		r    [][]map[string]interface{}
 	}{
 		{
 			name: `$$test1`,
-			rj: "{\"sql\": \"SELECT echo(count) as e, countPlusOne(count) as p FROM ext where count > 49\",\"actions\": [{\"file\":  {\"path\":\"" + CACHE_FILE + "\"}}]}",
-
+			rj:   "{\"sql\": \"SELECT echo(count) as e, countPlusOne(count) as p FROM ext where count > 49\",\"actions\": [{\"file\":  {\"path\":\"" + CACHE_FILE + "\"}}]}",
 		},
 	}
 	fmt.Printf("The test bucket size is %d.\n\n", len(tests))
@@ -66,7 +66,7 @@ func TestExtensions(t *testing.T) {
 		}
 
 		tp, err := rp.ExecInitRule(rs)
-		if err != nil{
+		if err != nil {
 			t.Errorf("fail to init rule: %v", err)
 			continue
 		}
@@ -81,13 +81,13 @@ func TestExtensions(t *testing.T) {
 		time.Sleep(5000 * time.Millisecond)
 		log.Printf("exit main program after 5 seconds")
 		results := getResults()
-		if len(results) == 0{
+		if len(results) == 0 {
 			t.Errorf("no result found")
 			continue
 		}
 		log.Debugf("get results %v", results)
 		var maps [][]map[string]interface{}
-		for _, v := range results{
+		for _, v := range results {
 			var mapRes []map[string]interface{}
 			err := json.Unmarshal([]byte(v), &mapRes)
 			if err != nil {
@@ -97,14 +97,14 @@ func TestExtensions(t *testing.T) {
 			maps = append(maps, mapRes)
 		}
 
-		for _, r := range maps{
-			if len(r) != 1{
+		for _, r := range maps {
+			if len(r) != 1 {
 				t.Errorf("%d. %q\n\nresult mismatch:\n\ngot=%#v\n\n", i, tt.rj, maps)
 				break
 			}
 			r := r[0]
 			e := int((r["e"]).(float64))
-			if e != 50 && e != 51{
+			if e != 50 && e != 51 {
 				t.Errorf("%d. %q\n\nresult mismatch:\n\ngot=%#v\n\n", i, tt.rj, maps)
 				break
 			}
@@ -118,15 +118,15 @@ func TestExtensions(t *testing.T) {
 	}
 }
 
-func getResults() []string{
+func getResults() []string {
 	f, err := os.Open(CACHE_FILE)
-	if err != nil{
+	if err != nil {
 		panic(err)
 	}
 	defer f.Close()
 	result := make([]string, 0)
 	scanner := bufio.NewScanner(f)
-	for scanner.Scan(){
+	for scanner.Scan() {
 		result = append(result, scanner.Text())
 	}
 	if err := scanner.Err(); err != nil {

+ 121 - 72
xsql/processors/xsql_processor.go

@@ -18,60 +18,56 @@ import (
 var log = common.Log
 
 type StreamProcessor struct {
-	statement string
-	dbDir     string
+	db common.KeyValue
 }
 
-//@params s : the sql string of create stream statement
 //@params d : the directory of the DB to save the stream info
-func NewStreamProcessor(s, d string) *StreamProcessor {
+func NewStreamProcessor(d string) *StreamProcessor {
 	processor := &StreamProcessor{
-		statement: s,
-		dbDir:     d,
+		db: common.GetSimpleKVStore(d),
 	}
 	return processor
 }
 
-func (p *StreamProcessor) Exec() (result []string, err error) {
-	parser := xsql.NewParser(strings.NewReader(p.statement))
+func (p *StreamProcessor) ExecStmt(statement string) (result []string, err error) {
+	parser := xsql.NewParser(strings.NewReader(statement))
 	stmt, err := xsql.Language.Parse(parser)
 	if err != nil {
-		return
-	}
-
-	store := common.GetSimpleKVStore(p.dbDir)
-	err = store.Open()
-	if err != nil {
-		return
+		return nil, err
 	}
-	defer store.Close()
-
 	switch s := stmt.(type) {
 	case *xsql.StreamStmt:
 		var r string
-		r, err = p.execCreateStream(s, store)
+		r, err = p.execCreateStream(s, statement)
 		result = append(result, r)
 	case *xsql.ShowStreamsStatement:
-		result, err = p.execShowStream(s, store)
+		result, err = p.execShowStream(s)
 	case *xsql.DescribeStreamStatement:
 		var r string
-		r, err = p.execDescribeStream(s, store)
+		r, err = p.execDescribeStream(s)
 		result = append(result, r)
 	case *xsql.ExplainStreamStatement:
 		var r string
-		r, err = p.execExplainStream(s, store)
+		r, err = p.execExplainStream(s)
 		result = append(result, r)
 	case *xsql.DropStreamStatement:
 		var r string
-		r, err = p.execDropStream(s, store)
+		r, err = p.execDropStream(s)
 		result = append(result, r)
+	default:
+		return nil, fmt.Errorf("Invalid stream statement: %s", statement)
 	}
 
 	return
 }
 
-func (p *StreamProcessor) execCreateStream(stmt *xsql.StreamStmt, db common.KeyValue) (string, error) {
-	err := db.Set(string(stmt.Name), p.statement)
+func (p *StreamProcessor) execCreateStream(stmt *xsql.StreamStmt, statement string) (string, error) {
+	err := p.db.Open()
+	if err != nil {
+		return "", fmt.Errorf("Create stream fails, error when opening db: %v.", err)
+	}
+	defer p.db.Close()
+	err = p.db.Set(string(stmt.Name), statement)
 	if err != nil {
 		return "", fmt.Errorf("Create stream fails: %v.", err)
 	} else {
@@ -79,26 +75,36 @@ func (p *StreamProcessor) execCreateStream(stmt *xsql.StreamStmt, db common.KeyV
 	}
 }
 
-func (p *StreamProcessor) execShowStream(stmt *xsql.ShowStreamsStatement, db common.KeyValue) ([]string, error) {
-	keys, err := db.Keys()
+func (p *StreamProcessor) ExecStreamSql(statement string) (string, error) {
+	r, err := p.ExecStmt(statement)
+	if err != nil {
+		return "", err
+	} else {
+		return strings.Join(r, "\n"), err
+	}
+}
+
+func (p *StreamProcessor) execShowStream(stmt *xsql.ShowStreamsStatement) ([]string, error) {
+	keys, err := p.ShowStream()
 	if len(keys) == 0 {
 		keys = append(keys, "No stream definitions are found.")
 	}
 	return keys, err
 }
 
-func (p *StreamProcessor) execDescribeStream(stmt *xsql.DescribeStreamStatement, db common.KeyValue) (string, error) {
-	s, f := db.Get(stmt.Name)
-	s1, _ := s.(string)
-	if !f {
-		return "", fmt.Errorf("Stream %s is not found.", stmt.Name)
+func (p *StreamProcessor) ShowStream() ([]string, error) {
+	err := p.db.Open()
+	if err != nil {
+		return nil, fmt.Errorf("Show stream fails, error when opening db: %v.", err)
 	}
+	defer p.db.Close()
+	return p.db.Keys()
+}
 
-	parser := xsql.NewParser(strings.NewReader(s1))
-	stream, err := xsql.Language.Parse(parser)
-	streamStmt, ok := stream.(*xsql.StreamStmt)
-	if !ok {
-		return "", fmt.Errorf("Error resolving the stream %s, the data in db may be corrupted.", stmt.Name)
+func (p *StreamProcessor) execDescribeStream(stmt *xsql.DescribeStreamStatement) (string, error) {
+	streamStmt, err := p.DescStream(stmt.Name)
+	if err != nil {
+		return "", err
 	}
 	var buff bytes.Buffer
 	buff.WriteString("Fields\n--------------------------------------------------------------------------------\n")
@@ -112,20 +118,58 @@ func (p *StreamProcessor) execDescribeStream(stmt *xsql.DescribeStreamStatement,
 	return buff.String(), err
 }
 
-func (p *StreamProcessor) execExplainStream(stmt *xsql.ExplainStreamStatement, db common.KeyValue) (string, error) {
-	_, f := db.Get(stmt.Name)
+func (p *StreamProcessor) DescStream(name string) (*xsql.StreamStmt, error) {
+	err := p.db.Open()
+	if err != nil {
+		return nil, fmt.Errorf("Describe stream fails, error when opening db: %v.", err)
+	}
+	defer p.db.Close()
+	s, f := p.db.Get(name)
+	if !f {
+		return nil, fmt.Errorf("Stream %s is not found.", name)
+	}
+	s1 := s.(string)
+
+	parser := xsql.NewParser(strings.NewReader(s1))
+	stream, err := xsql.Language.Parse(parser)
+	if err != nil {
+		return nil, err
+	}
+	streamStmt, ok := stream.(*xsql.StreamStmt)
+	if !ok {
+		return nil, fmt.Errorf("Error resolving the stream %s, the data in db may be corrupted.", name)
+	}
+	return streamStmt, nil
+}
+
+func (p *StreamProcessor) execExplainStream(stmt *xsql.ExplainStreamStatement) (string, error) {
+	err := p.db.Open()
+	if err != nil {
+		return "", fmt.Errorf("Explain stream fails, error when opening db: %v.", err)
+	}
+	defer p.db.Close()
+	_, f := p.db.Get(stmt.Name)
 	if !f {
 		return "", fmt.Errorf("Stream %s is not found.", stmt.Name)
 	}
 	return "TO BE SUPPORTED", nil
 }
 
-func (p *StreamProcessor) execDropStream(stmt *xsql.DropStreamStatement, db common.KeyValue) (string, error) {
-	err := db.Delete(stmt.Name)
+func (p *StreamProcessor) execDropStream(stmt *xsql.DropStreamStatement) (string, error) {
+	return p.DropStream(stmt.Name)
+}
+
+func (p *StreamProcessor) DropStream(name string) (string, error) {
+	err := p.db.Open()
+	if err != nil {
+		return "", fmt.Errorf("Drop stream fails, error when opening db: %v.", err)
+	}
+	defer p.db.Close()
+	err = p.db.Delete(name)
 	if err != nil {
 		return "", fmt.Errorf("Drop stream fails: %v.", err)
 	} else {
-		return fmt.Sprintf("Stream %s is dropped.", stmt.Name), nil
+		return fmt.Sprintf("Stream %s is dropped.", name), nil
 	}
 }
 
@@ -145,12 +189,14 @@ func GetStream(m *common.SimpleKVStore, name string) (stmt *xsql.StreamStmt, err
 }
 
 type RuleProcessor struct {
-	dbDir string
+	db        common.KeyValue
+	rootDbDir string
 }
 
 func NewRuleProcessor(d string) *RuleProcessor {
 	processor := &RuleProcessor{
-		dbDir: d,
+		db:        common.GetSimpleKVStore(path.Join(d, "rule")),
+		rootDbDir: d,
 	}
 	return processor
 }
@@ -160,30 +206,30 @@ func (p *RuleProcessor) ExecCreate(name, ruleJson string) (*api.Rule, error) {
 	if err != nil {
 		return nil, err
 	}
-	store := common.GetSimpleKVStore(path.Join(p.dbDir, "rule"))
-	err = store.Open()
+
+	err = p.db.Open()
 	if err != nil {
 		return nil, err
 	}
-	err = store.Set(string(name), ruleJson)
-	defer store.Close()
+	defer p.db.Close()
+
+	err = p.db.Set(rule.Id, ruleJson)
 	if err != nil {
 		return nil, err
 	} else {
-		log.Infof("Rule %s is created.", name)
+		log.Infof("Rule %s is created.", rule.Id)
 	}
 
 	return rule, nil
 }
 
 func (p *RuleProcessor) GetRuleByName(name string) (*api.Rule, error) {
-	store := common.GetSimpleKVStore(path.Join(p.dbDir, "rule"))
-	err := store.Open()
+	err := p.db.Open()
 	if err != nil {
 		return nil, err
 	}
-	defer store.Close()
-	s, f := store.Get(string(name))
+	defer p.db.Close()
+	s, f := p.db.Get(string(name))
 	if !f {
 		return nil, fmt.Errorf("Rule %s is not found.", name)
 	}
@@ -196,11 +242,17 @@ func (p *RuleProcessor) getRuleByJson(name, ruleJson string) (*api.Rule, error)
 	if err := json.Unmarshal([]byte(ruleJson), &rule); err != nil {
 		return nil, fmt.Errorf("Parse rule %s error : %s.", ruleJson, err)
 	}
-	rule.Id = name
+
 	//validation
-	if name == "" {
+	if rule.Id == "" && name == "" {
 		return nil, fmt.Errorf("Missing rule id.")
 	}
+	if name != "" && rule.Id != "" && name != rule.Id {
+		return nil, fmt.Errorf("Name is not consistent with rule id.")
+	}
+	if rule.Id == "" {
+		rule.Id = name
+	}
 	if rule.Sql == "" {
 		return nil, fmt.Errorf("Missing rule SQL.")
 	}
@@ -231,7 +283,7 @@ func (p *RuleProcessor) ExecQuery(ruleid, sql string) (*xstream.TopologyNew, err
 	if tp, inputs, err := p.createTopo(&api.Rule{Id: ruleid, Sql: sql}); err != nil {
 		return nil, err
 	} else {
-		tp.AddSink(inputs, nodes.NewSinkNode("sink_memory_log", "logToMemory", nil ))
+		tp.AddSink(inputs, nodes.NewSinkNode("sink_memory_log", "logToMemory", nil))
 		go func() {
 			select {
 			case err := <-tp.Open():
@@ -245,13 +297,12 @@ func (p *RuleProcessor) ExecQuery(ruleid, sql string) (*xstream.TopologyNew, err
 }
 
 func (p *RuleProcessor) ExecDesc(name string) (string, error) {
-	store := common.GetSimpleKVStore(path.Join(p.dbDir, "rule"))
-	err := store.Open()
+	err := p.db.Open()
 	if err != nil {
 		return "", err
 	}
-	defer store.Close()
-	s, f := store.Get(string(name))
+	defer p.db.Close()
+	s, f := p.db.Get(name)
 	if !f {
 		return "", fmt.Errorf("Rule %s is not found.", name)
 	}
@@ -280,23 +331,21 @@ func (p *RuleProcessor) ExecShow() (string, error) {
 }
 
 func (p *RuleProcessor) GetAllRules() ([]string, error) {
-	store := common.GetSimpleKVStore(path.Join(p.dbDir, "rule"))
-	err := store.Open()
+	err := p.db.Open()
 	if err != nil {
 		return nil, err
 	}
-	defer store.Close()
-	return store.Keys()
+	defer p.db.Close()
+	return p.db.Keys()
 }
 
 func (p *RuleProcessor) ExecDrop(name string) (string, error) {
-	store := common.GetSimpleKVStore(path.Join(p.dbDir, "rule"))
-	err := store.Open()
+	err := p.db.Open()
 	if err != nil {
 		return "", err
 	}
-	defer store.Close()
-	err = store.Delete(string(name))
+	defer p.db.Close()
+	err = p.db.Delete(string(name))
 	if err != nil {
 		return "", err
 	} else {
@@ -313,9 +362,9 @@ func (p *RuleProcessor) createTopoWithSources(rule *api.Rule, sources []*nodes.S
 	name := rule.Id
 	sql := rule.Sql
 	var (
-		isEventTime bool
-		lateTol int64
-		concurrency = 1
+		isEventTime  bool
+		lateTol      int64
+		concurrency  = 1
 		bufferLength = 1024
 	)
 
@@ -363,7 +412,7 @@ func (p *RuleProcessor) createTopoWithSources(rule *api.Rule, sources []*nodes.S
 			if !shouldCreateSource && len(streamsFromStmt) != len(sources) {
 				return nil, nil, fmt.Errorf("Invalid parameter sources or streams, the length cannot match the statement, expect %d sources.", len(streamsFromStmt))
 			}
-			store := common.GetSimpleKVStore(path.Join(p.dbDir, "stream"))
+			store := common.GetSimpleKVStore(path.Join(p.rootDbDir, "stream"))
 			err := store.Open()
 			if err != nil {
 				return nil, nil, err
@@ -456,4 +505,4 @@ func (p *RuleProcessor) createTopoWithSources(rule *api.Rule, sources []*nodes.S
 			return tp, inputs, nil
 		}
 	}
-}
+}

+ 188 - 194
xsql/processors/xsql_processor_test.go

@@ -5,6 +5,7 @@ import (
 	"fmt"
 	"github.com/emqx/kuiper/common"
 	"github.com/emqx/kuiper/xsql"
+	"github.com/emqx/kuiper/xstream"
 	"github.com/emqx/kuiper/xstream/api"
 	"github.com/emqx/kuiper/xstream/nodes"
 	"github.com/emqx/kuiper/xstream/test"
@@ -89,24 +90,25 @@ func TestStreamCreateProcessor(t *testing.T) {
 
 	streamDB := path.Join(getDbDir(), "streamTest")
 	for i, tt := range tests {
-		results, err := NewStreamProcessor(tt.s, streamDB).Exec()
+		results, err := NewStreamProcessor(streamDB).ExecStmt(tt.s)
 		if !reflect.DeepEqual(tt.err, errstring(err)) {
 			t.Errorf("%d. %q: error mismatch:\n  exp=%s\n  got=%s\n\n", i, tt.s, tt.err, err)
 		} else if tt.err == "" {
 			if !reflect.DeepEqual(tt.r, results) {
-				t.Errorf("%d. %q\n\nstmt mismatch:\nexp=%s\ngot=%#v\n\n", i, tt.s,tt.r, results)
+				t.Errorf("%d. %q\n\nstmt mismatch:\nexp=%s\ngot=%#v\n\n", i, tt.s, tt.r, results)
 			}
 		}
 	}
 }
 
 func createStreams(t *testing.T) {
+	p := NewStreamProcessor(path.Join(DbDir, "stream"))
 	demo := `CREATE STREAM demo (
 					color STRING,
 					size BIGINT,
 					ts BIGINT
 				) WITH (DATASOURCE="demo", FORMAT="json", KEY="ts");`
-	_, err := NewStreamProcessor(demo, path.Join(DbDir, "stream")).Exec()
+	_, err := p.ExecStmt(demo)
 	if err != nil {
 		t.Log(err)
 	}
@@ -115,7 +117,7 @@ func createStreams(t *testing.T) {
 					hum BIGINT,
 					ts BIGINT
 				) WITH (DATASOURCE="demo1", FORMAT="json", KEY="ts");`
-	_, err = NewStreamProcessor(demo1, path.Join(DbDir, "stream")).Exec()
+	_, err = p.ExecStmt(demo1)
 	if err != nil {
 		t.Log(err)
 	}
@@ -124,31 +126,32 @@ func createStreams(t *testing.T) {
 					hum BIGINT,
 					ts BIGINT
 				) WITH (DATASOURCE="sessionDemo", FORMAT="json", KEY="ts");`
-	_, err = NewStreamProcessor(sessionDemo, path.Join(DbDir, "stream")).Exec()
+	_, err = p.ExecStmt(sessionDemo)
 	if err != nil {
 		t.Log(err)
 	}
 }
 
 func dropStreams(t *testing.T) {
+	p := NewStreamProcessor(path.Join(DbDir, "stream"))
 	demo := `DROP STREAM demo`
-	_, err := NewStreamProcessor(demo, path.Join(DbDir, "stream")).Exec()
+	_, err := p.ExecStmt(demo)
 	if err != nil {
 		t.Log(err)
 	}
 	demo1 := `DROP STREAM demo1`
-	_, err = NewStreamProcessor(demo1, path.Join(DbDir, "stream")).Exec()
+	_, err = p.ExecStmt(demo1)
 	if err != nil {
 		t.Log(err)
 	}
 	sessionDemo := `DROP STREAM sessionDemo`
-	_, err = NewStreamProcessor(sessionDemo, path.Join(DbDir, "stream")).Exec()
+	_, err = p.ExecStmt(sessionDemo)
 	if err != nil {
 		t.Log(err)
 	}
 }
 
-func getMockSource(name string, done chan<- struct{}, size int) *nodes.SourceNode {
+func getMockSource(name string, done <-chan int, size int) *nodes.SourceNode {
 	var data []*xsql.Tuple
 	switch name {
 	case "demo":
@@ -360,6 +363,7 @@ func TestSingleSQL(t *testing.T) {
 		name string
 		sql  string
 		r    [][]map[string]interface{}
+		s    string
 		m    map[string]interface{}
 	}{
 		{
@@ -403,14 +407,15 @@ func TestSingleSQL(t *testing.T) {
 				"op_project_0_records_in_total":   int64(5),
 				"op_project_0_records_out_total":  int64(5),
 
-				"sink_MockSink_0_exceptions_total":  int64(0),
-				"sink_MockSink_0_records_in_total":  int64(5),
-				"sink_MockSink_0_records_out_total": int64(5),
+				"sink_mockSink_0_exceptions_total":  int64(0),
+				"sink_mockSink_0_records_in_total":  int64(5),
+				"sink_mockSink_0_records_out_total": int64(5),
 
 				"source_demo_0_exceptions_total":  int64(0),
 				"source_demo_0_records_in_total":  int64(5),
 				"source_demo_0_records_out_total": int64(5),
 			},
+			s: "sink_mockSink_0_records_out_total",
 		}, {
 			name: `rule2`,
 			sql:  `SELECT color, ts FROM demo where size > 3`,
@@ -424,6 +429,7 @@ func TestSingleSQL(t *testing.T) {
 					"ts":    float64(1541152488442),
 				}},
 			},
+			s: "op_filter_0_records_in_total",
 			m: map[string]interface{}{
 				"op_preprocessor_demo_0_exceptions_total":   int64(0),
 				"op_preprocessor_demo_0_process_latency_ms": int64(0),
@@ -435,9 +441,9 @@ func TestSingleSQL(t *testing.T) {
 				"op_project_0_records_in_total":   int64(2),
 				"op_project_0_records_out_total":  int64(2),
 
-				"sink_MockSink_0_exceptions_total":  int64(0),
-				"sink_MockSink_0_records_in_total":  int64(2),
-				"sink_MockSink_0_records_out_total": int64(2),
+				"sink_mockSink_0_exceptions_total":  int64(0),
+				"sink_mockSink_0_records_in_total":  int64(2),
+				"sink_mockSink_0_records_out_total": int64(2),
 
 				"source_demo_0_exceptions_total":  int64(0),
 				"source_demo_0_records_in_total":  int64(5),
@@ -461,6 +467,7 @@ func TestSingleSQL(t *testing.T) {
 					"ts":   float64(1541152488442),
 				}},
 			},
+			s: "op_filter_0_records_in_total",
 			m: map[string]interface{}{
 				"op_preprocessor_demo_0_exceptions_total":   int64(0),
 				"op_preprocessor_demo_0_process_latency_ms": int64(0),
@@ -472,9 +479,9 @@ func TestSingleSQL(t *testing.T) {
 				"op_project_0_records_in_total":   int64(2),
 				"op_project_0_records_out_total":  int64(2),
 
-				"sink_MockSink_0_exceptions_total":  int64(0),
-				"sink_MockSink_0_records_in_total":  int64(2),
-				"sink_MockSink_0_records_out_total": int64(2),
+				"sink_mockSink_0_exceptions_total":  int64(0),
+				"sink_mockSink_0_records_in_total":  int64(2),
+				"sink_mockSink_0_records_out_total": int64(2),
 
 				"source_demo_0_exceptions_total":  int64(0),
 				"source_demo_0_records_in_total":  int64(5),
@@ -490,12 +497,15 @@ func TestSingleSQL(t *testing.T) {
 	fmt.Printf("The test bucket size is %d.\n\n", len(tests))
 	createStreams(t)
 	defer dropStreams(t)
-	done := make(chan struct{})
 	//defer close(done)
 	for i, tt := range tests {
+		test.ResetClock(1541152486000)
 		p := NewRuleProcessor(DbDir)
 		parser := xsql.NewParser(strings.NewReader(tt.sql))
-		var sources []*nodes.SourceNode
+		var (
+			sources []*nodes.SourceNode
+			syncs   []chan int
+		)
 		if stmt, err := xsql.Language.Parse(parser); err != nil {
 			t.Errorf("parse sql %s error: %s", tt.sql, err)
 		} else {
@@ -504,41 +514,40 @@ func TestSingleSQL(t *testing.T) {
 			} else {
 				streams := xsql.GetStreams(selectStmt)
 				for _, stream := range streams {
-					source := getMockSource(stream, done, 5)
+					next := make(chan int)
+					syncs = append(syncs, next)
+					source := getMockSource(stream, next, 5)
 					sources = append(sources, source)
 				}
 			}
 		}
-		tp, inputs, err := p.createTopoWithSources(&api.Rule{Id: tt.name, Sql: tt.sql, Options:map[string]interface{}{
+		tp, inputs, err := p.createTopoWithSources(&api.Rule{Id: tt.name, Sql: tt.sql, Options: map[string]interface{}{
 			"bufferLength": float64(100),
 		}}, sources)
 		if err != nil {
 			t.Error(err)
 		}
 		mockSink := test.NewMockSink()
-		sink := nodes.NewSinkNodeWithSink("MockSink", mockSink)
+		sink := nodes.NewSinkNodeWithSink("mockSink", mockSink)
 		tp.AddSink(inputs, sink)
-		count := len(sources)
 		errCh := tp.Open()
 		func() {
-			for {
+			for i := 0; i < 5; i++ {
+				syncs[i%len(syncs)] <- i
 				select {
 				case err = <-errCh:
 					t.Log(err)
 					tp.Cancel()
 					return
-				case <-done:
-					count--
-					log.Infof("%d sources remaining", count)
-					if count <= 0 {
-						log.Info("stream stopping")
-						time.Sleep(1 * time.Second)
-						tp.Cancel()
-						return
-					}
 				default:
 				}
 			}
+			for retry := 100; retry > 0; retry-- {
+				if err := compareMetrics(tp, tt.m, tt.sql); err == nil {
+					break
+				}
+				time.Sleep(time.Duration(retry) * time.Millisecond)
+			}
 		}()
 		results := mockSink.GetResults()
 		var maps [][]map[string]interface{}
@@ -551,40 +560,18 @@ func TestSingleSQL(t *testing.T) {
 			}
 			maps = append(maps, mapRes)
 		}
-		keys, values := tp.GetMetrics()
-		//for i, k := range keys{
-		//	log.Printf("%s:%v", k, values[i])
-		//}
-		for k, v := range tt.m {
-			var(
-				index int
-				key   string
-				matched bool
-			)
-			for index, key = range keys{
-				if k == key {
-					if values[index] == v{
-						matched = true
-					}
-					break
-				}
-			}
-			if matched{
-				continue
-			}
-			//do not find
-			if index < len(values){
-				t.Errorf("%d. %q\n\nmetrics mismatch for %s:\n\nexp=%#v(%t)\n\ngot=%#v(%t)\n\n", i, tt.sql, k, v, v, values[index], values[index])
-			}else{
-				t.Errorf("%d. %q\n\nmetrics mismatch for %s:\n\nexp=%#v\n\ngot=nil\n\n", i, tt.sql, k, v)
-			}
-			break
+		if !reflect.DeepEqual(tt.r, maps) {
+			t.Errorf("%d. %q\n\nresult mismatch:\n\nexp=%#v\n\ngot=%#v\n\n", i, tt.sql, tt.r, maps)
+			continue
+		}
+		if err := compareMetrics(tp, tt.m, tt.sql); err != nil {
+			t.Errorf("%d. %q\n\n%v", i, tt.sql, err)
 		}
+		tp.Cancel()
 	}
 }
 
 func TestWindow(t *testing.T) {
-	common.IsTesting = true
 	var tests = []struct {
 		name string
 		sql  string
@@ -970,13 +957,14 @@ func TestWindow(t *testing.T) {
 	fmt.Printf("The test bucket size is %d.\n\n", len(tests))
 	createStreams(t)
 	defer dropStreams(t)
-	done := make(chan struct{})
-	defer close(done)
-	common.ResetMockTicker()
 	for i, tt := range tests {
+		test.ResetClock(1541152486000)
 		p := NewRuleProcessor(DbDir)
 		parser := xsql.NewParser(strings.NewReader(tt.sql))
-		var sources []*nodes.SourceNode
+		var (
+			sources []*nodes.SourceNode
+			syncs   []chan int
+		)
 		if stmt, err := xsql.Language.Parse(parser); err != nil {
 			t.Errorf("parse sql %s error: %s", tt.sql, err)
 		} else {
@@ -985,7 +973,9 @@ func TestWindow(t *testing.T) {
 			} else {
 				streams := xsql.GetStreams(selectStmt)
 				for _, stream := range streams {
-					source := getMockSource(stream, done, tt.size)
+					next := make(chan int)
+					syncs = append(syncs, next)
+					source := getMockSource(stream, next, tt.size)
 					sources = append(sources, source)
 				}
 			}
@@ -997,25 +987,36 @@ func TestWindow(t *testing.T) {
 		mockSink := test.NewMockSink()
 		sink := nodes.NewSinkNodeWithSink("mockSink", mockSink)
 		tp.AddSink(inputs, sink)
-		count := len(sources)
 		errCh := tp.Open()
 		func() {
-			for {
+			for i := 0; i < tt.size*len(syncs); i++ {
+				syncs[i%len(syncs)] <- i
+				for {
+					time.Sleep(1)
+					if getMetric(tp, "op_window_0_records_in_total") == (i + 1) {
+						break
+					}
+				}
 				select {
 				case err = <-errCh:
 					t.Log(err)
 					tp.Cancel()
 					return
-				case <-done:
-					count--
-					log.Infof("%d sources remaining", count)
-					if count <= 0 {
-						log.Info("stream stopping")
-						return
-					}
 				default:
 				}
 			}
+			retry := 100
+			for ; retry > 0; retry-- {
+				if err := compareMetrics(tp, tt.m, tt.sql); err == nil {
+					break
+				}
+				t.Logf("wait to try another %d times", retry)
+				time.Sleep(time.Duration(retry) * time.Millisecond)
+			}
+			if retry == 0 {
+				err := compareMetrics(tp, tt.m, tt.sql)
+				t.Errorf("could not get correct metrics: %v", err)
+			}
 		}()
 		results := mockSink.GetResults()
 		var maps [][]map[string]interface{}
@@ -1031,43 +1032,21 @@ func TestWindow(t *testing.T) {
 		if !reflect.DeepEqual(tt.r, maps) {
 			t.Errorf("%d. %q\n\nresult mismatch:\n\nexp=%#v\n\ngot=%#v\n\n", i, tt.sql, tt.r, maps)
 		}
-		keys, values := tp.GetMetrics()
-		for k, v := range tt.m {
-			var(
-				index int
-				key   string
-				matched bool
-			)
-			for index, key = range keys{
-				if k == key {
-					if values[index] == v{
-						matched = true
-					}
-					break
-				}
-			}
-			if matched{
-				continue
-			}
-			//do not find
-			if index < len(values){
-				t.Errorf("%d. %q\n\nmetrics mismatch for %s:\n\nexp=%#v(%t)\n\ngot=%#v(%t)\n\n", i, tt.sql, k, v, v, values[index], values[index])
-			}else{
-				t.Errorf("%d. %q\n\nmetrics mismatch for %s:\n\nexp=%#v\n\ngot=nil\n\n", i, tt.sql, k, v)
-			}
-			break
+		if err := compareMetrics(tp, tt.m, tt.sql); err != nil {
+			t.Errorf("%d. %q\n\n%v", i, tt.sql, err)
 		}
 		tp.Cancel()
 	}
 }
 
 func createEventStreams(t *testing.T) {
+	p := NewStreamProcessor(path.Join(DbDir, "stream"))
 	demo := `CREATE STREAM demoE (
 					color STRING,
 					size BIGINT,
 					ts BIGINT
 				) WITH (DATASOURCE="demoE", FORMAT="json", KEY="ts", TIMESTAMP="ts");`
-	_, err := NewStreamProcessor(demo, path.Join(DbDir, "stream")).Exec()
+	_, err := p.ExecStmt(demo)
 	if err != nil {
 		t.Log(err)
 	}
@@ -1076,7 +1055,7 @@ func createEventStreams(t *testing.T) {
 					hum BIGINT,
 					ts BIGINT
 				) WITH (DATASOURCE="demo1E", FORMAT="json", KEY="ts", TIMESTAMP="ts");`
-	_, err = NewStreamProcessor(demo1, path.Join(DbDir, "stream")).Exec()
+	_, err = p.ExecStmt(demo1)
 	if err != nil {
 		t.Log(err)
 	}
@@ -1085,31 +1064,32 @@ func createEventStreams(t *testing.T) {
 					hum BIGINT,
 					ts BIGINT
 				) WITH (DATASOURCE="sessionDemoE", FORMAT="json", KEY="ts", TIMESTAMP="ts");`
-	_, err = NewStreamProcessor(sessionDemo, path.Join(DbDir, "stream")).Exec()
+	_, err = p.ExecStmt(sessionDemo)
 	if err != nil {
 		t.Log(err)
 	}
 }
 
 func dropEventStreams(t *testing.T) {
+	p := NewStreamProcessor(path.Join(DbDir, "stream"))
 	demo := `DROP STREAM demoE`
-	_, err := NewStreamProcessor(demo, path.Join(DbDir, "stream")).Exec()
+	_, err := p.ExecStmt(demo)
 	if err != nil {
 		t.Log(err)
 	}
 	demo1 := `DROP STREAM demo1E`
-	_, err = NewStreamProcessor(demo1, path.Join(DbDir, "stream")).Exec()
+	_, err = p.ExecStmt(demo1)
 	if err != nil {
 		t.Log(err)
 	}
 	sessionDemo := `DROP STREAM sessionDemoE`
-	_, err = NewStreamProcessor(sessionDemo, path.Join(DbDir, "stream")).Exec()
+	_, err = p.ExecStmt(sessionDemo)
 	if err != nil {
 		t.Log(err)
 	}
 }
 
-func getEventMockSource(name string, done chan<- struct{}, size int) *nodes.SourceNode {
+func getEventMockSource(name string, done <-chan int, size int) *nodes.SourceNode {
 	var data []*xsql.Tuple
 	switch name {
 	case "demoE":
@@ -1344,7 +1324,6 @@ func getEventMockSource(name string, done chan<- struct{}, size int) *nodes.Sour
 }
 
 func TestEventWindow(t *testing.T) {
-	common.IsTesting = true
 	var tests = []struct {
 		name string
 		sql  string
@@ -1404,9 +1383,9 @@ func TestEventWindow(t *testing.T) {
 				"op_project_0_records_in_total":   int64(5),
 				"op_project_0_records_out_total":  int64(5),
 
-				"sink_MockSink_0_exceptions_total":  int64(0),
-				"sink_MockSink_0_records_in_total":  int64(5),
-				"sink_MockSink_0_records_out_total": int64(5),
+				"sink_mockSink_0_exceptions_total":  int64(0),
+				"sink_mockSink_0_records_in_total":  int64(5),
+				"sink_mockSink_0_records_out_total": int64(5),
 
 				"source_demoE_0_exceptions_total":  int64(0),
 				"source_demoE_0_records_in_total":  int64(6),
@@ -1442,9 +1421,9 @@ func TestEventWindow(t *testing.T) {
 				"op_project_0_records_in_total":   int64(2),
 				"op_project_0_records_out_total":  int64(2),
 
-				"sink_MockSink_0_exceptions_total":  int64(0),
-				"sink_MockSink_0_records_in_total":  int64(2),
-				"sink_MockSink_0_records_out_total": int64(2),
+				"sink_mockSink_0_exceptions_total":  int64(0),
+				"sink_mockSink_0_records_in_total":  int64(2),
+				"sink_mockSink_0_records_out_total": int64(2),
 
 				"source_demoE_0_exceptions_total":  int64(0),
 				"source_demoE_0_records_in_total":  int64(6),
@@ -1511,9 +1490,9 @@ func TestEventWindow(t *testing.T) {
 				"op_project_0_records_in_total":   int64(5),
 				"op_project_0_records_out_total":  int64(5),
 
-				"sink_MockSink_0_exceptions_total":  int64(0),
-				"sink_MockSink_0_records_in_total":  int64(5),
-				"sink_MockSink_0_records_out_total": int64(5),
+				"sink_mockSink_0_exceptions_total":  int64(0),
+				"sink_mockSink_0_records_in_total":  int64(5),
+				"sink_mockSink_0_records_out_total": int64(5),
 
 				"source_demoE_0_exceptions_total":  int64(0),
 				"source_demoE_0_records_in_total":  int64(6),
@@ -1567,9 +1546,9 @@ func TestEventWindow(t *testing.T) {
 				"op_project_0_records_in_total":   int64(4),
 				"op_project_0_records_out_total":  int64(4),
 
-				"sink_MockSink_0_exceptions_total":  int64(0),
-				"sink_MockSink_0_records_in_total":  int64(4),
-				"sink_MockSink_0_records_out_total": int64(4),
+				"sink_mockSink_0_exceptions_total":  int64(0),
+				"sink_mockSink_0_records_in_total":  int64(4),
+				"sink_mockSink_0_records_out_total": int64(4),
 
 				"source_demoE_0_exceptions_total":  int64(0),
 				"source_demoE_0_records_in_total":  int64(6),
@@ -1628,9 +1607,9 @@ func TestEventWindow(t *testing.T) {
 				"op_project_0_records_in_total":   int64(4),
 				"op_project_0_records_out_total":  int64(4),
 
-				"sink_MockSink_0_exceptions_total":  int64(0),
-				"sink_MockSink_0_records_in_total":  int64(4),
-				"sink_MockSink_0_records_out_total": int64(4),
+				"sink_mockSink_0_exceptions_total":  int64(0),
+				"sink_mockSink_0_records_in_total":  int64(4),
+				"sink_mockSink_0_records_out_total": int64(4),
 
 				"source_sessionDemoE_0_exceptions_total":  int64(0),
 				"source_sessionDemoE_0_records_in_total":  int64(12),
@@ -1679,9 +1658,9 @@ func TestEventWindow(t *testing.T) {
 				"op_project_0_records_in_total":   int64(5),
 				"op_project_0_records_out_total":  int64(5),
 
-				"sink_MockSink_0_exceptions_total":  int64(0),
-				"sink_MockSink_0_records_in_total":  int64(5),
-				"sink_MockSink_0_records_out_total": int64(5),
+				"sink_mockSink_0_exceptions_total":  int64(0),
+				"sink_mockSink_0_records_in_total":  int64(5),
+				"sink_mockSink_0_records_out_total": int64(5),
 
 				"source_demoE_0_exceptions_total":  int64(0),
 				"source_demoE_0_records_in_total":  int64(6),
@@ -1705,36 +1684,14 @@ func TestEventWindow(t *testing.T) {
 	fmt.Printf("The test bucket size is %d.\n\n", len(tests))
 	createEventStreams(t)
 	defer dropEventStreams(t)
-	done := make(chan struct{})
-	defer close(done)
-	common.ResetMockTicker()
-	//mock ticker
-	realTicker := time.NewTicker(500 * time.Millisecond)
-	tickerDone := make(chan bool)
-	go func() {
-		ticker := common.GetTicker(1000).(*common.MockTicker)
-		timer := common.GetTimer(1000).(*common.MockTimer)
-		for {
-			select {
-			case <-tickerDone:
-				log.Infof("real ticker exiting...")
-				return
-			case t := <-realTicker.C:
-				ts := common.TimeToUnixMilli(t)
-				if ticker != nil {
-					go ticker.DoTick(ts)
-				}
-				if timer != nil {
-					go timer.DoTick(ts)
-				}
-			}
-		}
-
-	}()
 	for i, tt := range tests {
+		test.ResetClock(1541152486000)
 		p := NewRuleProcessor(DbDir)
 		parser := xsql.NewParser(strings.NewReader(tt.sql))
-		var sources []*nodes.SourceNode
+		var (
+			sources []*nodes.SourceNode
+			syncs   []chan int
+		)
 		if stmt, err := xsql.Language.Parse(parser); err != nil {
 			t.Errorf("parse sql %s error: %s", tt.sql, err)
 		} else {
@@ -1743,7 +1700,9 @@ func TestEventWindow(t *testing.T) {
 			} else {
 				streams := xsql.GetStreams(selectStmt)
 				for _, stream := range streams {
-					source := getEventMockSource(stream, done, tt.size)
+					next := make(chan int)
+					syncs = append(syncs, next)
+					source := getEventMockSource(stream, next, tt.size)
 					sources = append(sources, source)
 				}
 			}
@@ -1759,27 +1718,40 @@ func TestEventWindow(t *testing.T) {
 			t.Error(err)
 		}
 		mockSink := test.NewMockSink()
-		sink := nodes.NewSinkNodeWithSink("MockSink", mockSink)
+		sink := nodes.NewSinkNodeWithSink("mockSink", mockSink)
 		tp.AddSink(inputs, sink)
-		count := len(sources)
 		errCh := tp.Open()
 		func() {
-			for {
+			for i := 0; i < tt.size*len(syncs); i++ {
+				syncs[i%len(syncs)] <- i
+				for {
+					time.Sleep(1)
+					if getMetric(tp, "op_window_0_records_in_total") == (i + 1) {
+						break
+					}
+				}
 				select {
 				case err = <-errCh:
 					t.Log(err)
 					tp.Cancel()
 					return
-				case <-done:
-					count--
-					log.Infof("%d sources remaining", count)
-					if count <= 0 {
-						log.Info("stream stopping")
-						return
-					}
 				default:
 				}
 			}
+			mockClock := test.GetMockClock()
+			mockClock.Add(1000 * time.Millisecond)
+			retry := 100
+			for ; retry > 0; retry-- {
+				if err := compareMetrics(tp, tt.m, tt.sql); err == nil {
+					break
+				}
+				t.Logf("wait to try another %d times", retry)
+				time.Sleep(time.Duration(retry) * time.Millisecond)
+			}
+			if retry == 0 {
+				err := compareMetrics(tp, tt.m, tt.sql)
+				t.Errorf("could not get correct metrics: %v", err)
+			}
 		}()
 		results := mockSink.GetResults()
 		var maps [][]map[string]interface{}
@@ -1795,40 +1767,62 @@ func TestEventWindow(t *testing.T) {
 		if !reflect.DeepEqual(tt.r, maps) {
 			t.Errorf("%d. %q\n\nresult mismatch:\n\nexp=%#v\n\ngot=%#v\n\n", i, tt.sql, tt.r, maps)
 		}
-		keys, values := tp.GetMetrics()
-		//for i, k := range keys{
-		//	log.Printf("%s:%v", k, values[i])
-		//}
-		for k, v := range tt.m {
-			var(
-				index int
-				key   string
-				matched bool
-			)
-			for index, key = range keys{
-				if k == key {
-					if values[index] == v{
+		if err := compareMetrics(tp, tt.m, tt.sql); err != nil {
+			t.Errorf("%d. %q\n\n%v", i, tt.sql, err)
+		}
+		tp.Cancel()
+	}
+}
+
+func getMetric(tp *xstream.TopologyNew, name string) int {
+	keys, values := tp.GetMetrics()
+	for index, key := range keys {
+		if key == name {
+			return int(values[index].(int64))
+		}
+	}
+	fmt.Println("can't find " + name)
+	return 0
+}
+
+func compareMetrics(tp *xstream.TopologyNew, m map[string]interface{}, sql string) (err error) {
+	keys, values := tp.GetMetrics()
+	//for i, k := range keys{
+	//	log.Printf("%s:%v", k, values[i])
+	//}
+	for k, v := range m {
+		var (
+			index   int
+			key     string
+			matched bool
+		)
+		for index, key = range keys {
+			if k == key {
+				if strings.HasSuffix(k, "process_latency_ms") {
+					if values[index].(int64) >= v.(int64) {
 						matched = true
+						continue
+					} else {
+						break
 					}
-					break
 				}
+				if values[index] == v {
+					matched = true
+				}
+				break
 			}
-			if matched{
-				continue
-			}
-			//do not find
-			if index < len(values){
-				t.Errorf("%d. %q\n\nmetrics mismatch for %s:\n\nexp=%#v(%t)\n\ngot=%#v(%t)\n\n", i, tt.sql, k, v, v, values[index], values[index])
-			}else{
-				t.Errorf("%d. %q\n\nmetrics mismatch for %s:\n\nexp=%#v\n\ngot=nil\n\n", i, tt.sql, k, v)
-			}
-			break
 		}
-		tp.Cancel()
+		if matched {
+			continue
+		}
+		//do not find
+		if index < len(values) {
+			return fmt.Errorf("metrics mismatch for %s:\n\nexp=%#v(%t)\n\ngot=%#v(%t)\n\n", k, v, v, values[index], values[index])
+		} else {
+			return fmt.Errorf("metrics mismatch for %s:\n\nexp=%#v\n\ngot=nil\n\n", k, v)
+		}
 	}
-	realTicker.Stop()
-	tickerDone <- true
-	close(tickerDone)
+	return nil
 }
 
 func errstring(err error) string {

+ 7 - 7
xsql/util.go

@@ -13,7 +13,7 @@ func PrintFieldType(ft FieldType, buff *bytes.Buffer) {
 		buff.WriteString("array(")
 		if t.FieldType != nil {
 			PrintFieldType(t.FieldType, buff)
-		}else{
+		} else {
 			buff.WriteString(t.Type.String())
 		}
 		buff.WriteString(")")
@@ -21,9 +21,9 @@ func PrintFieldType(ft FieldType, buff *bytes.Buffer) {
 		buff.WriteString("struct(")
 		isFirst := true
 		for _, f := range t.StreamFields {
-			if isFirst{
+			if isFirst {
 				isFirst = false
-			}else{
+			} else {
 				buff.WriteString(", ")
 			}
 			buff.WriteString(f.Name + " ")
@@ -33,17 +33,17 @@ func PrintFieldType(ft FieldType, buff *bytes.Buffer) {
 	}
 }
 
-func GetStreams(stmt *SelectStatement) (result []string){
+func GetStreams(stmt *SelectStatement) (result []string) {
 	if stmt == nil {
 		return nil
 	}
-	for _, source := range stmt.Sources{
+	for _, source := range stmt.Sources {
 		if s, ok := source.(*Table); ok {
 			result = append(result, s.Name)
 		}
 	}
 
-	for _, join := range stmt.Joins{
+	for _, join := range stmt.Joins {
 		result = append(result, join.Name)
 	}
 	return
@@ -59,4 +59,4 @@ func LowercaseKeyMap(m map[string]interface{}) map[string]interface{} {
 		}
 	}
 	return m1
-}
+}

+ 2 - 3
xsql/xsql_manager.go

@@ -27,11 +27,10 @@ func (t *ParseTree) Handle(tok Token, fn func(*Parser) (Statement, error)) {
 	t.Keys = append(t.Keys, tok.String())
 }
 
-
 func (pt *ParseTree) Parse(p *Parser) (Statement, error) {
 	tok, _ := p.scanIgnoreWhitespace()
 	p.unscan()
-	if f, ok  := pt.Handlers[tok]; ok {
+	if f, ok := pt.Handlers[tok]; ok {
 		return f(p)
 	}
 	return nil, nil
@@ -61,4 +60,4 @@ func init() {
 	Language.Handle(DROP, func(p *Parser) (statement Statement, e error) {
 		return p.parseDropStreamsStmt()
 	})
-}
+}

+ 8 - 9
xsql/xsql_parser_tree_test.go

@@ -23,28 +23,28 @@ func TestParser_ParseTree(t *testing.T) {
 					{Name: "USERID", FieldType: &BasicType{Type: BIGINT}},
 				},
 				Options: map[string]string{
-					"DATASOURCE" : "users",
-					"FORMAT" : "JSON",
-					"KEY" : "USERID",
+					"DATASOURCE": "users",
+					"FORMAT":     "JSON",
+					"KEY":        "USERID",
 				},
 			},
 		},
 
 		{
-			s: `SHOW STREAMS`,
+			s:    `SHOW STREAMS`,
 			stmt: &ShowStreamsStatement{},
 		},
 
 		{
-			s: `SHOW STREAMSf`,
+			s:    `SHOW STREAMSf`,
 			stmt: nil,
-			err: `found "STREAMSf", expected keyword streams.`,
+			err:  `found "STREAMSf", expected keyword streams.`,
 		},
 
 		{
-			s: `SHOW STREAMS d`,
+			s:    `SHOW STREAMS d`,
 			stmt: nil,
-			err: `found "d", expected semecolon or EOF.`,
+			err:  `found "d", expected semecolon or EOF.`,
 		},
 
 		{
@@ -70,7 +70,6 @@ func TestParser_ParseTree(t *testing.T) {
 			},
 			err: ``,
 		},
-
 	}
 
 	fmt.Printf("The test bucket size is %d.\n\n", len(tests))

+ 52 - 53
xsql/xsql_stream_test.go

@@ -38,17 +38,17 @@ func TestParser_ParseCreateStream(t *testing.T) {
 					}},
 				},
 				Options: map[string]string{
-					"DATASOURCE" : "users",
-					"FORMAT" : "AVRO",
-					"KEY" : "USERID",
-					"CONF_KEY" : "srv1",
-					"TYPE" : "MQTT",
-					"TIMESTAMP" : "USERID",
-					"TIMESTAMP_FORMAT" : "yyyy-MM-dd''T''HH:mm:ssX'",
+					"DATASOURCE":       "users",
+					"FORMAT":           "AVRO",
+					"KEY":              "USERID",
+					"CONF_KEY":         "srv1",
+					"TYPE":             "MQTT",
+					"TIMESTAMP":        "USERID",
+					"TIMESTAMP_FORMAT": "yyyy-MM-dd''T''HH:mm:ssX'",
 				},
 			},
 		},
-		
+
 		{
 			s: `CREATE STREAM demo (
 					USERID BIGINT,
@@ -59,14 +59,14 @@ func TestParser_ParseCreateStream(t *testing.T) {
 					{Name: "USERID", FieldType: &BasicType{Type: BIGINT}},
 				},
 				Options: map[string]string{
-					"DATASOURCE" : "users",
-					"FORMAT" : "JSON",
-					"KEY" : "USERID",
-					"STRICT_VALIDATION" : "true",
+					"DATASOURCE":        "users",
+					"FORMAT":            "JSON",
+					"KEY":               "USERID",
+					"STRICT_VALIDATION": "true",
 				},
 			},
 		},
-		
+
 		{
 			s: `CREATE STREAM demo (
 					ADDRESSES ARRAY(STRUCT(STREET_NAME STRING, NUMBER BIGINT)),
@@ -85,14 +85,14 @@ func TestParser_ParseCreateStream(t *testing.T) {
 					}},
 				},
 				Options: map[string]string{
-					"DATASOURCE" : "users",
-					"FORMAT" : "AVRO",
-					"KEY" : "USERID",
+					"DATASOURCE":        "users",
+					"FORMAT":            "AVRO",
+					"KEY":               "USERID",
 					"STRICT_VALIDATION": "FAlse",
 				},
 			},
 		},
-		
+
 		{
 			s: `CREATE STREAM demo (
 					ADDRESSES ARRAY(STRUCT(STREET_NAME STRING, NUMBER BIGINT)),
@@ -113,13 +113,13 @@ func TestParser_ParseCreateStream(t *testing.T) {
 					{Name: "birthday", FieldType: &BasicType{Type: DATETIME}},
 				},
 				Options: map[string]string{
-					"DATASOURCE" : "users",
-					"FORMAT" : "AVRO",
-					"KEY" : "USERID",
+					"DATASOURCE": "users",
+					"FORMAT":     "AVRO",
+					"KEY":        "USERID",
 				},
 			},
 		},
-		
+
 		{
 			s: `CREATE STREAM demo (
 					NAME string,
@@ -142,28 +142,28 @@ func TestParser_ParseCreateStream(t *testing.T) {
 					{Name: "birthday", FieldType: &BasicType{Type: DATETIME}},
 				},
 				Options: map[string]string{
-					"DATASOURCE" : "users",
-					"FORMAT" : "AVRO",
-					"KEY" : "USERID",
+					"DATASOURCE": "users",
+					"FORMAT":     "AVRO",
+					"KEY":        "USERID",
 				},
 			},
 		},
-		
+
 		{
 			s: `CREATE STREAM demo (
 		
 				) WITH (DATASOURCE="users", FORMAT="JSON", KEY="USERID");`,
 			stmt: nil,
-			err: `found ")", expect stream field name.`,
+			err:  `found ")", expect stream field name.`,
 		},
 
 		{
 			s: `CREATE STREAM demo (NAME string)
 				 WITH (DATASOURCE="users", FORMAT="JSON", KEY="USERID", STRICT_VALIDATION="true1");`, //Invalid STRICT_VALIDATION value
 			stmt: nil,
-			err: `found "true1", expect TRUE/FALSE value in STRICT_VALIDATION option.`,
+			err:  `found "true1", expect TRUE/FALSE value in STRICT_VALIDATION option.`,
 		},
-		
+
 		{
 			s: `CREATE STREAM demo (NAME string) WITH (DATASOURCE="users", FORMAT="JSON", KEY="USERID");`,
 			stmt: &StreamStmt{
@@ -172,63 +172,63 @@ func TestParser_ParseCreateStream(t *testing.T) {
 					{Name: "NAME", FieldType: &BasicType{Type: STRINGS}},
 				},
 				Options: map[string]string{
-					"DATASOURCE" : "users",
-					"FORMAT" : "JSON",
-					"KEY" : "USERID",
+					"DATASOURCE": "users",
+					"FORMAT":     "JSON",
+					"KEY":        "USERID",
 				},
 			},
 		},
-		
+
 		{
 			s: `CREATE STREAM demo (NAME string)) WITH (DATASOURCE="users", FORMAT="JSON", KEY="USERID");`,
 			stmt: &StreamStmt{
-				Name: StreamName("demo"),
+				Name:         StreamName("demo"),
 				StreamFields: nil,
-				Options: nil,
+				Options:      nil,
 			},
 			err: `found ")", expect stream options.`,
 		},
-		
+
 		{
 			s: `CREATE STREAM demo (NAME string) WITHs (DATASOURCE="users", FORMAT="JSON", KEY="USERID");`,
 			stmt: &StreamStmt{
-				Name: StreamName("demo"),
+				Name:         StreamName("demo"),
 				StreamFields: nil,
-				Options: nil,
+				Options:      nil,
 			},
 			err: `found "WITHs", expected is with.`,
 		},
-		
+
 		{
 			s: `CREATE STREAM demo (NAME integer) WITH (DATASOURCE="users", FORMAT="JSON", KEY="USERID");`,
 			stmt: &StreamStmt{
-				Name: "demo",
+				Name:         "demo",
 				StreamFields: nil,
-				Options: nil,
+				Options:      nil,
 			},
 			err: `found "integer", expect valid stream field types(BIGINT | FLOAT | STRINGS | DATETIME | BOOLEAN | ARRAY | STRUCT).`,
 		},
-		
+
 		{
 			s: `CREATE STREAM demo (NAME string) WITH (sources="users", FORMAT="JSON", KEY="USERID");`,
 			stmt: &StreamStmt{
-				Name: "demo",
+				Name:         "demo",
 				StreamFields: nil,
-				Options: nil,
+				Options:      nil,
 			},
 			err: `found "sources", unknown option keys(DATASOURCE|FORMAT|KEY|CONF_KEY|STRICT_VALIDATION|TYPE).`,
 		},
-		
+
 		{
 			s: `CREATE STREAM demo ((NAME string) WITH (DATASOURCE="users", FORMAT="JSON", KEY="USERID");`,
 			stmt: &StreamStmt{
-				Name: "demo",
+				Name:         "demo",
 				StreamFields: nil,
-				Options: nil,
+				Options:      nil,
 			},
 			err: `found "(", expect stream field name.`,
 		},
-		
+
 		{
 			s: `CREATE STREAM demo (
 					USERID BIGINT,
@@ -247,9 +247,9 @@ func TestParser_ParseCreateStream(t *testing.T) {
 					USERID BIGINT,
 				) WITH ());`,
 			stmt: &StreamStmt{
-				Name: "",
+				Name:         "",
 				StreamFields: nil,
-				Options: nil,
+				Options:      nil,
 			},
 			err: `found ")", expected semicolon or EOF.`,
 		},
@@ -259,13 +259,12 @@ func TestParser_ParseCreateStream(t *testing.T) {
 					USERID BIGINT,
 				) WITH DATASOURCE="users", FORMAT="JSON", KEY="USERID");`,
 			stmt: &StreamStmt{
-				Name: "",
+				Name:         "",
 				StreamFields: nil,
-				Options: nil,
+				Options:      nil,
 			},
 			//TODO The error string should be more accurate
 			err: `found "DATASOURCE", expect stream options.`,
-
 		},
 	}
 
@@ -287,4 +286,4 @@ func TestParser_ParseCreateStream(t *testing.T) {
 //		return err.Error()
 //	}
 //	return ""
-//}
+//}

+ 25 - 4
xstream/api/stream.go

@@ -4,9 +4,30 @@ import (
 	"context"
 )
 
-//The function to call when data is emitted by the source.
-type ConsumeFunc func(message map[string]interface{}, metadata map[string]interface{})
-type ErrorFunc func(err error)
+type SourceTuple interface {
+	Message() map[string]interface{}
+	Meta() map[string]interface{}
+}
+
+type DefaultSourceTuple struct {
+	message map[string]interface{}
+	meta    map[string]interface{}
+}
+
+func NewDefaultSourceTuple(message map[string]interface{}, meta map[string]interface{}) *DefaultSourceTuple {
+	return &DefaultSourceTuple{
+		message: message,
+		meta:    meta,
+	}
+}
+
+func (t *DefaultSourceTuple) Message() map[string]interface{} {
+	return t.message
+}
+func (t *DefaultSourceTuple) Meta() map[string]interface{} {
+	return t.meta
+}
+
 type Logger interface {
 	Debug(args ...interface{})
 	Info(args ...interface{})
@@ -28,7 +49,7 @@ type Closable interface {
 
 type Source interface {
 	//Should be sync function for normal case. The container will run it in go func
-	Open(ctx StreamContext, consume ConsumeFunc, onError ErrorFunc)
+	Open(ctx StreamContext, consumer chan<- SourceTuple, errCh chan<- error)
 	//Called during initialization. Configure the source with the data source(e.g. topic for mqtt) and the properties
 	//read from the yaml
 	Configure(datasource string, props map[string]interface{}) error

+ 48 - 50
xstream/cli/main.go

@@ -2,8 +2,8 @@ package main
 
 import (
 	"bufio"
-	"github.com/emqx/kuiper/common"
 	"fmt"
+	"github.com/emqx/kuiper/common"
 	"github.com/go-yaml/yaml"
 	"github.com/urfave/cli"
 	"io/ioutil"
@@ -16,20 +16,20 @@ import (
 
 type clientConf struct {
 	Host string `yaml:"host"`
-	Port int `yaml:"port"`
+	Port int    `yaml:"port"`
 }
 
 var clientYaml = "client.yaml"
 
-func streamProcess(client *rpc.Client, args string)  {
+func streamProcess(client *rpc.Client, args string) {
 	var reply string
-	if args == ""{
+	if args == "" {
 		args = strings.Join(os.Args[1:], " ")
 	}
 	err := client.Call("Server.Stream", args, &reply)
-	if err != nil{
+	if err != nil {
 		fmt.Println(err)
-	}else{
+	} else {
 		fmt.Println(reply)
 	}
 }
@@ -53,11 +53,11 @@ func main() {
 	var config *clientConf
 	if err := yaml.Unmarshal(b, &cfg); err != nil {
 		fmt.Printf("Failed to load config file with error %s.\n", err)
-	}else{
+	} else {
 		c, ok := cfg["basic"]
-		if !ok{
+		if !ok {
 			fmt.Printf("No basic config in client.yaml, will use the default configuration.\n")
-		}else{
+		} else {
 			config = &c
 		}
 	}
@@ -78,10 +78,10 @@ func main() {
 
 	app.Commands = []cli.Command{
 		{
-			Name:      "query",
-			Aliases:   []string{"query"},
-			Usage:     "query command line",
-			Action:    func(c *cli.Context) error {
+			Name:    "query",
+			Aliases: []string{"query"},
+			Usage:   "query command line",
+			Action: func(c *cli.Context) error {
 				reader := bufio.NewReader(os.Stdin)
 				var inputs []string
 				ticker := time.NewTicker(time.Millisecond * 300)
@@ -101,7 +101,7 @@ func main() {
 					} else {
 						var reply string
 						err := client.Call("Server.CreateQuery", text, &reply)
-						if err != nil{
+						if err != nil {
 							fmt.Println(err)
 							continue
 						} else {
@@ -128,18 +128,18 @@ func main() {
 			},
 		},
 		{
-			Name:      "create",
-			Aliases:   []string{"create"},
-			Usage:     "create stream $stream_name | create stream $stream_name -f $stream_def_file | create rule $rule_name $rule_json | create rule $rule_name -f $rule_def_file",
+			Name:    "create",
+			Aliases: []string{"create"},
+			Usage:   "create stream $stream_name | create stream $stream_name -f $stream_def_file | create rule $rule_name $rule_json | create rule $rule_name -f $rule_def_file",
 
-			Subcommands: []cli.Command {
+			Subcommands: []cli.Command{
 				{
 					Name:  "stream",
 					Usage: "create stream $stream_name [-f stream_def_file]",
-					Flags: []cli.Flag {
+					Flags: []cli.Flag{
 						cli.StringFlag{
-							Name: "file, f",
-							Usage: "the location of stream definition file",
+							Name:     "file, f",
+							Usage:    "the location of stream definition file",
 							FilePath: "/home/mystream.txt",
 						},
 					},
@@ -168,10 +168,10 @@ func main() {
 				{
 					Name:  "rule",
 					Usage: "create rule $rule_name [$rule_json | -f rule_def_file]",
-					Flags: []cli.Flag {
+					Flags: []cli.Flag{
 						cli.StringFlag{
-							Name: "file, f",
-							Usage: "the location of rule definition file",
+							Name:     "file, f",
+							Usage:    "the location of rule definition file",
 							FilePath: "/home/myrule.txt",
 						},
 					},
@@ -222,12 +222,11 @@ func main() {
 					},
 				},
 			},
-
 		},
 		{
-			Name:      "describe",
-			Aliases:   []string{"describe"},
-			Usage:     "describe stream $stream_name | describe rule $rule_name",
+			Name:    "describe",
+			Aliases: []string{"describe"},
+			Usage:   "describe stream $stream_name | describe rule $rule_name",
 			Subcommands: []cli.Command{
 				{
 					Name:  "stream",
@@ -241,7 +240,7 @@ func main() {
 				{
 					Name:  "rule",
 					Usage: "describe rule $rule_name",
-					Action:    func(c *cli.Context) error {
+					Action: func(c *cli.Context) error {
 						if len(c.Args()) != 1 {
 							fmt.Printf("Expect rule name.\n")
 							return nil
@@ -261,9 +260,9 @@ func main() {
 		},
 
 		{
-			Name:        "drop",
-			Aliases:     []string{"drop"},
-			Usage:       "drop stream $stream_name | drop rule $rule_name",
+			Name:    "drop",
+			Aliases: []string{"drop"},
+			Usage:   "drop stream $stream_name | drop rule $rule_name",
 			Subcommands: []cli.Command{
 				{
 					Name:  "stream",
@@ -298,9 +297,9 @@ func main() {
 		},
 
 		{
-			Name:      "show",
-			Aliases:   []string{"show"},
-			Usage:     "show streams | show rules",
+			Name:    "show",
+			Aliases: []string{"show"},
+			Usage:   "show streams | show rules",
 
 			Subcommands: []cli.Command{
 				{
@@ -314,7 +313,7 @@ func main() {
 				{
 					Name:  "rules",
 					Usage: "show rules",
-					Action:    func(c *cli.Context) error {
+					Action: func(c *cli.Context) error {
 						var reply string
 						err = client.Call("Server.ShowRules", 0, &reply)
 						if err != nil {
@@ -329,9 +328,9 @@ func main() {
 		},
 
 		{
-			Name:        "getstatus",
-			Aliases:     []string{"getstatus"},
-			Usage:       "getstatus rule $rule_name",
+			Name:    "getstatus",
+			Aliases: []string{"getstatus"},
+			Usage:   "getstatus rule $rule_name",
 			Subcommands: []cli.Command{
 				{
 					Name:  "rule",
@@ -356,9 +355,9 @@ func main() {
 			},
 		},
 		{
-			Name:        "start",
-			Aliases:     []string{"start"},
-			Usage:       "start rule $rule_name",
+			Name:    "start",
+			Aliases: []string{"start"},
+			Usage:   "start rule $rule_name",
 			Subcommands: []cli.Command{
 				{
 					Name:  "rule",
@@ -383,9 +382,9 @@ func main() {
 			},
 		},
 		{
-			Name:        "stop",
-			Aliases:     []string{"stop"},
-			Usage:       "stop rule $rule_name",
+			Name:    "stop",
+			Aliases: []string{"stop"},
+			Usage:   "stop rule $rule_name",
 			Subcommands: []cli.Command{
 				{
 					Name:  "rule",
@@ -410,9 +409,9 @@ func main() {
 			},
 		},
 		{
-			Name:        "restart",
-			Aliases:     []string{"restart"},
-			Usage:       "restart rule $rule_name",
+			Name:    "restart",
+			Aliases: []string{"restart"},
+			Usage:   "restart rule $rule_name",
 			Subcommands: []cli.Command{
 				{
 					Name:  "rule",
@@ -438,7 +437,6 @@ func main() {
 		},
 	}
 
-
 	app.Name = "Kuiper"
 	app.Usage = "The command line tool for EMQ X Kuiper."
 
@@ -456,4 +454,4 @@ func main() {
 	if err != nil {
 		fmt.Printf("%v", err)
 	}
-}
+}

+ 4 - 4
xstream/collectors/func.go

@@ -1,8 +1,8 @@
 package collectors
 
 import (
-	"github.com/emqx/kuiper/xstream/api"
 	"errors"
+	"github.com/emqx/kuiper/xstream/api"
 )
 
 // CollectorFunc is a function used to colllect
@@ -15,7 +15,7 @@ type CollectorFunc func(api.StreamContext, interface{}) error
 // of type:
 //   CollectorFunc
 type FuncCollector struct {
-	f     CollectorFunc
+	f CollectorFunc
 }
 
 // Func creates a new value *FuncCollector that
@@ -25,7 +25,7 @@ func Func(f CollectorFunc) *FuncCollector {
 	return &FuncCollector{f: f}
 }
 
-func (c *FuncCollector) Configure(props map[string]interface{}) error{
+func (c *FuncCollector) Configure(props map[string]interface{}) error {
 	//do nothing
 	return nil
 }
@@ -47,4 +47,4 @@ func (c *FuncCollector) Collect(ctx api.StreamContext, item interface{}) error {
 
 func (c *FuncCollector) Close(api.StreamContext) error {
 	return nil
-}
+}

+ 1 - 2
xstream/contexts/default.go

@@ -40,7 +40,7 @@ func (c *DefaultContext) Done() <-chan struct{} {
 }
 
 func (c *DefaultContext) Err() error {
-	if c.err != nil{
+	if c.err != nil {
 		return c.err
 	}
 	return c.ctx.Err()
@@ -79,7 +79,6 @@ func (c *DefaultContext) SetError(err error) {
 	c.err = err
 }
 
-
 func (c *DefaultContext) WithMeta(ruleId string, opId string) api.StreamContext {
 	return &DefaultContext{
 		ruleId:     ruleId,

+ 2 - 3
xstream/demo/func_visitor.go

@@ -1,10 +1,9 @@
 package main
 
 import (
-	"github.com/emqx/kuiper/xsql"
 	"fmt"
+	"github.com/emqx/kuiper/xsql"
 	"strings"
-
 )
 
 func main() {
@@ -12,7 +11,7 @@ func main() {
 
 	var srcs []string
 	xsql.WalkFunc(stmt.Joins, func(node xsql.Node) {
-		if f,ok := node.(*xsql.FieldRef); ok {
+		if f, ok := node.(*xsql.FieldRef); ok {
 			if string(f.StreamName) == "" {
 				return
 			}

+ 15 - 10
xstream/extensions/mqtt_source.go

@@ -70,13 +70,13 @@ func (ms *MQTTSource) Configure(topic string, props map[string]interface{}) erro
 	return nil
 }
 
-func (ms *MQTTSource) Open(ctx api.StreamContext, consume api.ConsumeFunc, onError api.ErrorFunc) {
+func (ms *MQTTSource) Open(ctx api.StreamContext, consumer chan<- api.SourceTuple, errCh chan<- error) {
 	log := ctx.GetLogger()
 
 	opts := MQTT.NewClientOptions().AddBroker(ms.srv).SetProtocolVersion(ms.pVersion)
 	if ms.clientid == "" {
 		if uuid, err := uuid.NewUUID(); err != nil {
-			onError(fmt.Errorf("failed to get uuid, the error is %s", err))
+			errCh <- fmt.Errorf("failed to get uuid, the error is %s", err)
 		} else {
 			ms.clientid = uuid.String()
 			opts.SetClientID(uuid.String())
@@ -92,15 +92,15 @@ func (ms *MQTTSource) Open(ctx api.StreamContext, consume api.ConsumeFunc, onErr
 			if kp, err1 := common.ProcessPath(ms.pkeyPath); err1 == nil {
 				log.Infof("The private key file is %s.", kp)
 				if cer, err2 := tls.LoadX509KeyPair(cp, kp); err2 != nil {
-					onError(err2)
+					errCh <- err2
 				} else {
 					opts.SetTLSConfig(&tls.Config{Certificates: []tls.Certificate{cer}})
 				}
 			} else {
-				onError(err1)
+				errCh <- err1
 			}
 		} else {
-			onError(err)
+			errCh <- err
 		}
 	} else {
 		log.Infof("Connect MQTT broker with username and password.")
@@ -121,7 +121,7 @@ func (ms *MQTTSource) Open(ctx api.StreamContext, consume api.ConsumeFunc, onErr
 	opts.SetConnectionLostHandler(func(client MQTT.Client, e error) {
 		log.Errorf("The connection %s is disconnected due to error %s, will try to re-connect later.", ms.srv+": "+ms.clientid, e)
 		reconn = true
-		subscribe(ms.tpc, client, ctx, consume)
+		subscribe(ms.tpc, client, ctx, consumer)
 	})
 
 	opts.SetOnConnectHandler(func(client MQTT.Client) {
@@ -132,15 +132,15 @@ func (ms *MQTTSource) Open(ctx api.StreamContext, consume api.ConsumeFunc, onErr
 
 	c := MQTT.NewClient(opts)
 	if token := c.Connect(); token.Wait() && token.Error() != nil {
-		onError(fmt.Errorf("found error when connecting to %s: %s", ms.srv, token.Error()))
+		errCh <- fmt.Errorf("found error when connecting to %s: %s", ms.srv, token.Error())
 	}
 	log.Infof("The connection to server %s was established successfully", ms.srv)
 	ms.conn = c
-	subscribe(ms.tpc, c, ctx, consume)
+	subscribe(ms.tpc, c, ctx, consumer)
 	log.Infof("Successfully subscribe to topic %s", ms.srv+": "+ms.clientid)
 }
 
-func subscribe(topic string, client MQTT.Client, ctx api.StreamContext, consume api.ConsumeFunc) {
+func subscribe(topic string, client MQTT.Client, ctx api.StreamContext, consumer chan<- api.SourceTuple) {
 	log := ctx.GetLogger()
 	h := func(client MQTT.Client, msg MQTT.Message) {
 		log.Debugf("instance %d received %s", ctx.GetInstanceId(), msg.Payload())
@@ -156,7 +156,12 @@ func subscribe(topic string, client MQTT.Client, ctx api.StreamContext, consume
 		meta := make(map[string]interface{})
 		meta[xsql.INTERNAL_MQTT_TOPIC_KEY] = msg.Topic()
 		meta[xsql.INTERNAL_MQTT_MSG_ID_KEY] = strconv.Itoa(int(msg.MessageID()))
-		consume(result, meta)
+		select {
+		case consumer <- api.NewDefaultSourceTuple(result, meta):
+			log.Debugf("send data to source node")
+		case <-ctx.Done():
+			return
+		}
 	}
 
 	if token := client.Subscribe(topic, 0, h); token.Wait() && token.Error() != nil {

+ 1 - 1
xstream/funcs.go

@@ -2,9 +2,9 @@ package xstream
 
 import (
 	"context"
+	"fmt"
 	"github.com/emqx/kuiper/xstream/api"
 	"github.com/emqx/kuiper/xstream/operators"
-	"fmt"
 	"reflect"
 )
 

+ 6 - 8
xstream/nodes/common_func.go

@@ -8,17 +8,15 @@ import (
 //Blocking broadcast
 func Broadcast(outputs map[string]chan<- interface{}, val interface{}, ctx api.StreamContext) {
 	logger := ctx.GetLogger()
-	var barrier sync.WaitGroup
-	barrier.Add(len(outputs))
+	var wg sync.WaitGroup
+	wg.Add(len(outputs))
 	for n, out := range outputs {
-		go func(wg *sync.WaitGroup){
-			out <- val
+		go func(output chan<- interface{}) {
+			output <- val
 			wg.Done()
 			logger.Debugf("broadcast from %s to %s done", ctx.GetOpId(), n)
-		}(&barrier)
+		}(out)
 	}
 	logger.Debugf("broadcasting from %s", ctx.GetOpId())
-	barrier.Wait()
+	wg.Wait()
 }
-
-

+ 16 - 14
common/utils/dynamic_channel_buffer.go

@@ -1,24 +1,26 @@
-package utils
+package nodes
+
+import "github.com/emqx/kuiper/xstream/api"
 
 type DynamicChannelBuffer struct {
-	In chan interface{}
-	Out chan interface{}
-	buffer []interface{}
-	limit int
+	In     chan api.SourceTuple
+	Out    chan api.SourceTuple
+	buffer []api.SourceTuple
+	limit  int
 }
 
 func NewDynamicChannelBuffer() *DynamicChannelBuffer {
 	buffer := &DynamicChannelBuffer{
-		In: make(chan interface{}),
-		Out: make(chan interface{}),
-		buffer: make([]interface{}, 0),
-		limit: 102400,
+		In:     make(chan api.SourceTuple),
+		Out:    make(chan api.SourceTuple),
+		buffer: make([]api.SourceTuple, 0),
+		limit:  102400,
 	}
 	go buffer.run()
 	return buffer
 }
 
-func (b *DynamicChannelBuffer) SetLimit(limit int){
+func (b *DynamicChannelBuffer) SetLimit(limit int) {
 	if limit > 0 {
 		b.limit = limit
 	}
@@ -27,18 +29,18 @@ func (b *DynamicChannelBuffer) SetLimit(limit int){
 func (b *DynamicChannelBuffer) run() {
 	for {
 		l := len(b.buffer)
-		if l >= b.limit{
+		if l >= b.limit {
 			b.Out <- b.buffer[0]
 			b.buffer = b.buffer[1:]
-		}else if l > 0 {
+		} else if l > 0 {
 			select {
 			case b.Out <- b.buffer[0]:
 				b.buffer = b.buffer[1:]
-			case value := <- b.In:
+			case value := <-b.In:
 				b.buffer = append(b.buffer, value)
 			}
 		} else {
-			value := <- b.In
+			value := <-b.In
 			b.buffer = append(b.buffer, value)
 		}
 	}

+ 92 - 0
xstream/nodes/prometheus.go

@@ -0,0 +1,92 @@
+package nodes
+
+import (
+	"github.com/prometheus/client_golang/prometheus"
+	"sync"
+)
+
+const RecordsInTotal = "records_in_total"
+const RecordsOutTotal = "records_out_total"
+const ExceptionsTotal = "exceptions_total"
+const ProcessLatencyMs = "process_latency_ms"
+const LastInvocation = "last_invocation"
+const BufferLength = "buffer_length"
+
+var (
+	MetricNames        = []string{RecordsInTotal, RecordsOutTotal, ExceptionsTotal, ProcessLatencyMs, BufferLength, LastInvocation}
+	prometheuseMetrics *PrometheusMetrics
+	mutex              sync.RWMutex
+)
+
+func GetPrometheusMetrics() *PrometheusMetrics {
+	mutex.Lock()
+	if prometheuseMetrics == nil {
+		prometheuseMetrics = newPrometheusMetrics()
+	}
+	mutex.Unlock()
+	return prometheuseMetrics
+}
+
+type MetricGroup struct {
+	TotalRecordsIn  *prometheus.CounterVec
+	TotalRecordsOut *prometheus.CounterVec
+	TotalExceptions *prometheus.CounterVec
+	ProcessLatency  *prometheus.GaugeVec
+	BufferLength    *prometheus.GaugeVec
+}
+
+type PrometheusMetrics struct {
+	vecs []*MetricGroup
+}
+
+func newPrometheusMetrics() *PrometheusMetrics {
+	var (
+		labelNames = []string{"rule", "type", "op", "instance"}
+		prefixes   = []string{"kuiper_source", "kuiper_op", "kuiper_sink"}
+	)
+	var vecs []*MetricGroup
+	for _, prefix := range prefixes {
+		//prometheus initialization
+		totalRecordsIn := prometheus.NewCounterVec(prometheus.CounterOpts{
+			Name: prefix + "_" + RecordsInTotal,
+			Help: "Total number of messages received by the operation of " + prefix,
+		}, labelNames)
+		totalRecordsOut := prometheus.NewCounterVec(prometheus.CounterOpts{
+			Name: prefix + "_" + RecordsOutTotal,
+			Help: "Total number of messages published by the operation of " + prefix,
+		}, labelNames)
+		totalExceptions := prometheus.NewCounterVec(prometheus.CounterOpts{
+			Name: prefix + "_" + ExceptionsTotal,
+			Help: "Total number of user exceptions of " + prefix,
+		}, labelNames)
+		processLatency := prometheus.NewGaugeVec(prometheus.GaugeOpts{
+			Name: prefix + "_" + ProcessLatencyMs,
+			Help: "Process latency in millisecond of " + prefix,
+		}, labelNames)
+		bufferLength := prometheus.NewGaugeVec(prometheus.GaugeOpts{
+			Name: prefix + "_" + BufferLength,
+			Help: "The length of the plan buffer which is shared by all instances of " + prefix,
+		}, labelNames)
+		prometheus.MustRegister(totalRecordsIn, totalRecordsOut, totalExceptions, processLatency, bufferLength)
+		vecs = append(vecs, &MetricGroup{
+			TotalRecordsIn:  totalRecordsIn,
+			TotalRecordsOut: totalRecordsOut,
+			TotalExceptions: totalExceptions,
+			ProcessLatency:  processLatency,
+			BufferLength:    bufferLength,
+		})
+	}
+	return &PrometheusMetrics{vecs: vecs}
+}
+
+func (m *PrometheusMetrics) GetMetricsGroup(opType string) *MetricGroup {
+	switch opType {
+	case "source":
+		return m.vecs[0]
+	case "op":
+		return m.vecs[1]
+	case "sink":
+		return m.vecs[2]
+	}
+	return nil
+}

+ 200 - 0
xstream/nodes/sink_cache.go

@@ -0,0 +1,200 @@
+package nodes
+
+import (
+	"encoding/gob"
+	"fmt"
+	"github.com/emqx/kuiper/common"
+	"github.com/emqx/kuiper/xstream/api"
+	"github.com/prometheus/common/log"
+	"io"
+	"path"
+	"sort"
+	"strconv"
+)
+
+type CacheTuple struct {
+	index int
+	data  interface{}
+}
+
+type LinkedQueue struct {
+	Data map[int]interface{}
+	Tail int
+}
+
+func (l *LinkedQueue) append(item interface{}) {
+	l.Data[l.Tail] = item
+	l.Tail++
+}
+
+func (l *LinkedQueue) delete(index int) {
+	delete(l.Data, index)
+}
+
+func (l *LinkedQueue) reset() {
+	l.Tail = 0
+}
+
+func (l *LinkedQueue) length() int {
+	return len(l.Data)
+}
+
+func (l *LinkedQueue) clone() *LinkedQueue {
+	result := &LinkedQueue{
+		Data: make(map[int]interface{}),
+		Tail: l.Tail,
+	}
+	for k, v := range l.Data {
+		result.Data[k] = v
+	}
+	return result
+}
+
+type Cache struct {
+	//Data and control channels
+	in       <-chan interface{}
+	Out      chan *CacheTuple
+	Complete chan int
+	errorCh  chan<- error
+	//states
+	pending *LinkedQueue
+	//serialize
+	key     string //the key for current cache
+	store   common.KeyValue
+	changed bool
+	saved   int
+	//configs
+	limit        int
+	saveInterval int
+}
+
+const THRESHOLD int = 10
+
+func NewCache(in <-chan interface{}, limit int, saveInterval int, errCh chan<- error, ctx api.StreamContext) *Cache {
+	c := &Cache{
+		in:       in,
+		Out:      make(chan *CacheTuple, limit),
+		Complete: make(chan int),
+		errorCh:  errCh,
+
+		limit:        limit,
+		saveInterval: saveInterval,
+	}
+	go c.run(ctx)
+	return c
+}
+
+func (c *Cache) run(ctx api.StreamContext) {
+	logger := ctx.GetLogger()
+	dbDir, err := common.GetAndCreateDataLoc("sink")
+	logger.Debugf("cache saved to %s", dbDir)
+	if err != nil {
+		c.drainError(err)
+	}
+	c.store = common.GetSimpleKVStore(path.Join(dbDir, "cache"))
+	c.key = ctx.GetRuleId() + ctx.GetOpId() + strconv.Itoa(ctx.GetInstanceId())
+	//load cache
+	if err := c.loadCache(); err != nil {
+		go c.drainError(err)
+		return
+	}
+
+	ticker := common.GetTicker(c.saveInterval)
+	for {
+		select {
+		case item := <-c.in:
+			index := c.pending.Tail
+			c.pending.append(item)
+			//non blocking until limit exceeded
+			c.Out <- &CacheTuple{
+				index: index,
+				data:  item,
+			}
+			c.changed = true
+		case index := <-c.Complete:
+			c.pending.delete(index)
+			c.changed = true
+		case <-ticker.C:
+			l := c.pending.length()
+			if l == 0 {
+				c.pending.reset()
+			}
+			//If the data is still changing, only do a save when the cache has more than threshold to prevent too much file IO
+			//If the data is not changing in the time slot and have not saved before, save it. This is to prevent the
+			//data won't be saved as the cache never pass the threshold
+			if (c.changed && l > THRESHOLD) || (!c.changed && c.saved != l) {
+				logger.Infof("save cache for rule %s", ctx.GetRuleId())
+				clone := c.pending.clone()
+				go func() {
+					if err := c.saveCache(clone); err != nil {
+						logger.Debugf("%v", err)
+						c.drainError(err)
+					}
+				}()
+				c.saved = l
+			} else if c.changed {
+				c.saved = 0
+			}
+			c.changed = false
+		case <-ctx.Done():
+			if c.changed {
+				c.saveCache(c.pending)
+			}
+			return
+		}
+	}
+}
+
+func (c *Cache) loadCache() error {
+	c.pending = &LinkedQueue{
+		Data: make(map[int]interface{}),
+		Tail: 0,
+	}
+	gob.Register(c.pending)
+	err := c.store.Open()
+	if err != nil && err != io.EOF {
+		return err
+	}
+	defer c.store.Close()
+	if err == nil {
+		if t, f := c.store.Get(c.key); f {
+			if mt, ok := t.(*LinkedQueue); ok {
+				c.pending = mt
+				// To store the keys in slice in sorted order
+				var keys []int
+				for k := range mt.Data {
+					keys = append(keys, k)
+				}
+				sort.Ints(keys)
+				for _, k := range keys {
+					log.Debugf("send by cache %d", k)
+					c.Out <- &CacheTuple{
+						index: k,
+						data:  mt.Data[k],
+					}
+				}
+				return nil
+			} else {
+				return fmt.Errorf("load malform cache, found %t(%v)", t, t)
+			}
+		}
+	}
+	return nil
+}
+
+func (c *Cache) saveCache(p *LinkedQueue) error {
+	err := c.store.Open()
+	if err != nil {
+		return err
+	}
+	defer c.store.Close()
+	return c.store.Replace(c.key, p)
+}
+
+func (c *Cache) drainError(err error) {
+	c.errorCh <- err
+}
+
+func (c *Cache) Length() int {
+	return c.pending.length()
+}

+ 0 - 0
xstream/nodes/sink_node.go


部分文件因为文件数量过多而无法显示