From 1c7e94b16765b8f2b36b537cd3e314a3f49b58c9 Mon Sep 17 00:00:00 2001 From: rulego-team Date: Fri, 25 Jul 2025 12:05:21 +0800 Subject: [PATCH] refactor:Rename AddData to Emit --- README.md | 8 +- README_ZH.md | 8 +- doc.go | 350 +-- examples/advanced-functions/main.go | 4 +- examples/complex-nested-access/main.go | 874 +++---- examples/comprehensive-test/main.go | 856 +++---- examples/custom-functions-demo/main.go | 16 +- examples/nested-field-examples/main.go | 1250 +++++----- examples/non-aggregation/main.go | 686 +++--- examples/null-comparison-examples/main.go | 532 ++--- examples/persistence/main.go | 2 +- examples/simple-custom-functions/main.go | 8 +- examples/unified_config/demo.go | 436 ++-- examples/unified_config/window_config_demo.go | 148 +- stream/stream.go | 2 +- stream/stream_test.go | 20 +- streamsql.go | 85 +- streamsql_benchmark_test.go | 276 ++- streamsql_case_test.go | 18 +- streamsql_custom_functions_test.go | 16 +- streamsql_function_integration_test.go | 40 +- streamsql_is_null_test.go | 2066 ++++++++--------- streamsql_like_test.go | 1102 ++++----- streamsql_plugin_test.go | 10 +- streamsql_test.go | 72 +- 25 files changed, 4478 insertions(+), 4407 deletions(-) diff --git a/README.md b/README.md index 6eaa202..9daabfa 100644 --- a/README.md +++ b/README.md @@ -82,7 +82,7 @@ func main() { } // Handle real-time transformation results - ssql.Stream().AddSink(func(result interface{}) { + ssql.AddSink(func(result interface{}) { fmt.Printf("Real-time result: %+v\n", result) }) @@ -110,7 +110,7 @@ func main() { // Process data one by one, each will output results immediately for _, data := range sensorData { - ssql.Stream().AddData(data) + ssql.Emit(data) time.Sleep(100 * time.Millisecond) // Simulate real-time data arrival } @@ -273,7 +273,7 @@ func main() { } // Handle aggregation results - ssql.Stream().AddSink(func(result interface{}) { + ssql.AddSink(func(result interface{}) { fmt.Printf("Aggregation result: %+v\n", result) }) @@ -293,7 +293,7 @@ func main() { "timestamp": time.Now().Unix(), } - ssql.Stream().AddData(nestedData) + ssql.Emit(nestedData) } ``` diff --git a/README_ZH.md b/README_ZH.md index 531da43..49f20c6 100644 --- a/README_ZH.md +++ b/README_ZH.md @@ -85,7 +85,7 @@ func main() { } // 处理实时转换结果 - ssql.Stream().AddSink(func(result interface{}) { + ssql.AddSink(func(result interface{}) { fmt.Printf("实时处理结果: %+v\n", result) }) @@ -113,7 +113,7 @@ func main() { // 逐条处理数据,每条都会立即输出结果 for _, data := range sensorData { - ssql.Stream().AddData(data) + ssql.Emit(data) time.Sleep(100 * time.Millisecond) // 模拟实时数据到达 } @@ -289,7 +289,7 @@ func main() { } // 处理聚合结果 - ssql.Stream().AddSink(func(result interface{}) { + ssql.AddSink(func(result interface{}) { fmt.Printf("聚合结果: %+v\n", result) }) @@ -309,7 +309,7 @@ func main() { "timestamp": time.Now().Unix(), } - ssql.Stream().AddData(nestedData) + ssql.Emit(nestedData) } ``` diff --git a/doc.go b/doc.go index f649dde..dfbc29f 100644 --- a/doc.go +++ b/doc.go @@ -1,175 +1,175 @@ -/* - * Copyright 2025 The RuleGo Authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/* -Package streamsql 是一个轻量级的、基于 SQL 的物联网边缘流处理引擎。 - -StreamSQL 提供了高效的无界数据流处理和分析能力,支持多种窗口类型、聚合函数、 -自定义函数,以及与 RuleGo 生态的无缝集成。 - -# 核心特性 - -• 轻量级设计 - 纯内存操作,无外部依赖 -• SQL语法支持 - 使用熟悉的SQL语法处理流数据 -• 多种窗口类型 - 滑动窗口、滚动窗口、计数窗口、会话窗口 -• 丰富的聚合函数 - MAX, MIN, AVG, SUM, STDDEV, MEDIAN, PERCENTILE等 -• 插件式自定义函数 - 运行时动态注册,支持8种函数类型 -• RuleGo生态集成 - 利用RuleGo组件扩展输入输出源 - -# 入门示例 - -基本的流数据处理: - - package main - - import ( - "fmt" - "math/rand" - "time" - "github.com/rulego/streamsql" - ) - - func main() { - // 创建StreamSQL实例 - ssql := streamsql.New() - - // 定义SQL查询 - 每5秒按设备ID分组计算温度平均值 - sql := `SELECT deviceId, - AVG(temperature) as avg_temp, - MIN(humidity) as min_humidity, - window_start() as start, - window_end() as end - FROM stream - WHERE deviceId != 'device3' - GROUP BY deviceId, TumblingWindow('5s')` - - // 执行SQL,创建流处理任务 - err := ssql.Execute(sql) - if err != nil { - panic(err) - } - - // 添加结果处理回调 - ssql.Stream().AddSink(func(result interface{}) { - fmt.Printf("聚合结果: %v\n", result) - }) - - // 模拟发送流数据 - go func() { - ticker := time.NewTicker(1 * time.Second) - defer ticker.Stop() - - for { - select { - case <-ticker.C: - // 生成随机设备数据 - data := map[string]interface{}{ - "deviceId": fmt.Sprintf("device%d", rand.Intn(3)+1), - "temperature": 20.0 + rand.Float64()*10, - "humidity": 50.0 + rand.Float64()*20, - } - ssql.AddData(data) - } - } - }() - - // 运行30秒 - time.Sleep(30 * time.Second) - } - -# 窗口函数 - -StreamSQL 支持多种窗口类型: - - // 滚动窗口 - 每5秒一个独立窗口 - SELECT AVG(temperature) FROM stream GROUP BY TumblingWindow('5s') - - // 滑动窗口 - 窗口大小30秒,每10秒滑动一次 - SELECT MAX(temperature) FROM stream GROUP BY SlidingWindow('30s', '10s') - - // 计数窗口 - 每100条记录一个窗口 - SELECT COUNT(*) FROM stream GROUP BY CountingWindow(100) - - // 会话窗口 - 超时5分钟自动关闭会话 - SELECT user_id, COUNT(*) FROM stream GROUP BY user_id, SessionWindow('5m') - -# 自定义函数 - -StreamSQL 支持插件式自定义函数,运行时动态注册: - - // 注册温度转换函数 - functions.RegisterCustomFunction( - "fahrenheit_to_celsius", - functions.TypeConversion, - "温度转换", - "华氏度转摄氏度", - 1, 1, - func(ctx *functions.FunctionContext, args []interface{}) (interface{}, error) { - f, _ := functions.ConvertToFloat64(args[0]) - return (f - 32) * 5 / 9, nil - }, - ) - - // 立即在SQL中使用 - sql := `SELECT deviceId, - AVG(fahrenheit_to_celsius(temperature)) as avg_celsius - FROM stream GROUP BY deviceId, TumblingWindow('5s')` - -支持的自定义函数类型: -• TypeMath - 数学计算函数 -• TypeString - 字符串处理函数 -• TypeConversion - 类型转换函数 -• TypeDateTime - 时间日期函数 -• TypeAggregation - 聚合函数 -• TypeAnalytical - 分析函数 -• TypeWindow - 窗口函数 -• TypeCustom - 通用自定义函数 - -# 日志配置 - -StreamSQL 提供灵活的日志配置选项: - - // 设置日志级别 - ssql := streamsql.New(streamsql.WithLogLevel(logger.DEBUG)) - - // 输出到文件 - logFile, _ := os.OpenFile("app.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644) - ssql := streamsql.New(streamsql.WithLogOutput(logFile, logger.INFO)) - - // 禁用日志(生产环境) - ssql := streamsql.New(streamsql.WithDiscardLog()) - -# 性能配置 - -对于生产环境,建议进行以下配置: - - ssql := streamsql.New( - streamsql.WithDiscardLog(), // 禁用日志提升性能 - // 其他配置选项... - ) - -# 与RuleGo集成 - -StreamSQL可以与RuleGo规则引擎无缝集成,利用RuleGo丰富的组件生态: - - // TODO: 提供RuleGo集成示例 - -更多详细信息和高级用法,请参阅: -• 自定义函数开发指南: docs/CUSTOM_FUNCTIONS_GUIDE.md -• 快速入门指南: docs/FUNCTION_QUICK_START.md -• 完整示例: examples/ -*/ -package streamsql +/* + * Copyright 2025 The RuleGo Authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* +Package streamsql 是一个轻量级的、基于 SQL 的物联网边缘流处理引擎。 + +StreamSQL 提供了高效的无界数据流处理和分析能力,支持多种窗口类型、聚合函数、 +自定义函数,以及与 RuleGo 生态的无缝集成。 + +# 核心特性 + +• 轻量级设计 - 纯内存操作,无外部依赖 +• SQL语法支持 - 使用熟悉的SQL语法处理流数据 +• 多种窗口类型 - 滑动窗口、滚动窗口、计数窗口、会话窗口 +• 丰富的聚合函数 - MAX, MIN, AVG, SUM, STDDEV, MEDIAN, PERCENTILE等 +• 插件式自定义函数 - 运行时动态注册,支持8种函数类型 +• RuleGo生态集成 - 利用RuleGo组件扩展输入输出源 + +# 入门示例 + +基本的流数据处理: + + package main + + import ( + "fmt" + "math/rand" + "time" + "github.com/rulego/streamsql" + ) + + func main() { + // 创建StreamSQL实例 + ssql := streamsql.New() + + // 定义SQL查询 - 每5秒按设备ID分组计算温度平均值 + sql := `SELECT deviceId, + AVG(temperature) as avg_temp, + MIN(humidity) as min_humidity, + window_start() as start, + window_end() as end + FROM stream + WHERE deviceId != 'device3' + GROUP BY deviceId, TumblingWindow('5s')` + + // 执行SQL,创建流处理任务 + err := ssql.Execute(sql) + if err != nil { + panic(err) + } + + // 添加结果处理回调 + ssql.AddSink(func(result interface{}) { + fmt.Printf("聚合结果: %v\n", result) + }) + + // 模拟发送流数据 + go func() { + ticker := time.NewTicker(1 * time.Second) + defer ticker.Stop() + + for { + select { + case <-ticker.C: + // 生成随机设备数据 + data := map[string]interface{}{ + "deviceId": fmt.Sprintf("device%d", rand.Intn(3)+1), + "temperature": 20.0 + rand.Float64()*10, + "humidity": 50.0 + rand.Float64()*20, + } + ssql.Emit(data) + } + } + }() + + // 运行30秒 + time.Sleep(30 * time.Second) + } + +# 窗口函数 + +StreamSQL 支持多种窗口类型: + + // 滚动窗口 - 每5秒一个独立窗口 + SELECT AVG(temperature) FROM stream GROUP BY TumblingWindow('5s') + + // 滑动窗口 - 窗口大小30秒,每10秒滑动一次 + SELECT MAX(temperature) FROM stream GROUP BY SlidingWindow('30s', '10s') + + // 计数窗口 - 每100条记录一个窗口 + SELECT COUNT(*) FROM stream GROUP BY CountingWindow(100) + + // 会话窗口 - 超时5分钟自动关闭会话 + SELECT user_id, COUNT(*) FROM stream GROUP BY user_id, SessionWindow('5m') + +# 自定义函数 + +StreamSQL 支持插件式自定义函数,运行时动态注册: + + // 注册温度转换函数 + functions.RegisterCustomFunction( + "fahrenheit_to_celsius", + functions.TypeConversion, + "温度转换", + "华氏度转摄氏度", + 1, 1, + func(ctx *functions.FunctionContext, args []interface{}) (interface{}, error) { + f, _ := functions.ConvertToFloat64(args[0]) + return (f - 32) * 5 / 9, nil + }, + ) + + // 立即在SQL中使用 + sql := `SELECT deviceId, + AVG(fahrenheit_to_celsius(temperature)) as avg_celsius + FROM stream GROUP BY deviceId, TumblingWindow('5s')` + +支持的自定义函数类型: +• TypeMath - 数学计算函数 +• TypeString - 字符串处理函数 +• TypeConversion - 类型转换函数 +• TypeDateTime - 时间日期函数 +• TypeAggregation - 聚合函数 +• TypeAnalytical - 分析函数 +• TypeWindow - 窗口函数 +• TypeCustom - 通用自定义函数 + +# 日志配置 + +StreamSQL 提供灵活的日志配置选项: + + // 设置日志级别 + ssql := streamsql.New(streamsql.WithLogLevel(logger.DEBUG)) + + // 输出到文件 + logFile, _ := os.OpenFile("app.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644) + ssql := streamsql.New(streamsql.WithLogOutput(logFile, logger.INFO)) + + // 禁用日志(生产环境) + ssql := streamsql.New(streamsql.WithDiscardLog()) + +# 性能配置 + +对于生产环境,建议进行以下配置: + + ssql := streamsql.New( + streamsql.WithDiscardLog(), // 禁用日志提升性能 + // 其他配置选项... + ) + +# 与RuleGo集成 + +StreamSQL可以与RuleGo规则引擎无缝集成,利用RuleGo丰富的组件生态: + + // TODO: 提供RuleGo集成示例 + +更多详细信息和高级用法,请参阅: +• 自定义函数开发指南: docs/CUSTOM_FUNCTIONS_GUIDE.md +• 快速入门指南: docs/FUNCTION_QUICK_START.md +• 完整示例: examples/ +*/ +package streamsql diff --git a/examples/advanced-functions/main.go b/examples/advanced-functions/main.go index a99f783..ea0acdb 100644 --- a/examples/advanced-functions/main.go +++ b/examples/advanced-functions/main.go @@ -51,7 +51,7 @@ func main() { fmt.Println("✓ SQL执行成功") // 5. 添加结果监听器 - ssql.Stream().AddSink(func(result interface{}) { + ssql.AddSink(func(result interface{}) { fmt.Printf("📊 聚合结果: %v\n", result) }) @@ -69,7 +69,7 @@ func main() { for _, data := range sensorData { fmt.Printf(" 设备: %s, 温度: %.1f°F, 湿度: %.1f%%\n", data["device"], data["temperature"], data["humidity"]) - ssql.AddData(data) + ssql.Emit(data) } // 7. 等待处理完成 diff --git a/examples/complex-nested-access/main.go b/examples/complex-nested-access/main.go index b79bbb2..7d5f418 100644 --- a/examples/complex-nested-access/main.go +++ b/examples/complex-nested-access/main.go @@ -1,437 +1,437 @@ -package main - -import ( - "context" - "fmt" - "math/rand" - "sync" - "time" - - "github.com/rulego/streamsql" -) - -func main() { - fmt.Println("🔧 StreamSQL 复杂嵌套字段访问功能演示") - fmt.Println("=======================================") - - // 创建 StreamSQL 实例 - ssql := streamsql.New() - defer ssql.Stop() - - // 演示1: 数组索引访问 - fmt.Println("\n📊 演示1: 数组索引访问") - demonstrateArrayAccess(ssql) - - // 演示2: Map键访问 - fmt.Println("\n🗝️ 演示2: Map键访问") - demonstrateMapKeyAccess(ssql) - - // 演示3: 混合复杂访问 - fmt.Println("\n🔄 演示3: 混合复杂访问") - demonstrateComplexMixedAccess(ssql) - - // 演示4: 负数索引访问 - fmt.Println("\n⬅️ 演示4: 负数索引访问") - demonstrateNegativeIndexAccess(ssql) - - // 演示5: 数组索引聚合计算 - fmt.Println("\n📈 演示5: 数组索引聚合计算") - demonstrateArrayIndexAggregation(ssql) - - fmt.Println("\n✅ 演示完成!") -} - -// 演示数组索引访问 -func demonstrateArrayAccess(ssql *streamsql.Streamsql) { - // SQL查询:提取数组中的特定元素 - rsql := `SELECT device, - sensors[0].temperature as first_sensor_temp, - sensors[1].humidity as second_sensor_humidity, - data[2] as third_data_item - FROM stream` - - err := ssql.Execute(rsql) - if err != nil { - fmt.Printf("❌ SQL执行失败: %v\n", err) - return - } - - // 准备测试数据 - testData := []map[string]interface{}{ - { - "device": "工业传感器-001", - "sensors": []interface{}{ - map[string]interface{}{"temperature": 25.5, "humidity": 60.2}, - map[string]interface{}{"temperature": 26.8, "humidity": 58.7}, - map[string]interface{}{"temperature": 24.1, "humidity": 62.1}, - }, - "data": []interface{}{"status_ok", "battery_95%", "signal_strong", "location_A1"}, - "timestamp": time.Now().Unix(), - }, - { - "device": "环境监测器-002", - "sensors": []interface{}{ - map[string]interface{}{"temperature": 22.3, "humidity": 65.8}, - map[string]interface{}{"temperature": 23.1, "humidity": 63.2}, - }, - "data": []interface{}{"status_warning", "battery_78%", "signal_weak"}, - "timestamp": time.Now().Unix(), - }, - } - - var wg sync.WaitGroup - wg.Add(1) - - // 设置结果回调 - ssql.Stream().AddSink(func(result interface{}) { - defer wg.Done() - - fmt.Println(" 📋 数组索引访问结果:") - if resultSlice, ok := result.([]map[string]interface{}); ok { - for i, item := range resultSlice { - fmt.Printf(" 记录 %d:\n", i+1) - fmt.Printf(" 设备: %v\n", item["device"]) - fmt.Printf(" 第一个传感器温度: %v°C\n", item["first_sensor_temp"]) - fmt.Printf(" 第二个传感器湿度: %v%%\n", item["second_sensor_humidity"]) - fmt.Printf(" 第三个数据项: %v\n", item["third_data_item"]) - fmt.Println() - } - } - }) - - // 添加测试数据 - for _, data := range testData { - ssql.Stream().AddData(data) - } - - // 等待结果 - wg.Wait() -} - -// 演示Map键访问 -func demonstrateMapKeyAccess(ssql *streamsql.Streamsql) { - // SQL查询:使用字符串键访问Map数据 - rsql := `SELECT device_id, - config['host'] as server_host, - config["port"] as server_port, - settings['enable_ssl'] as ssl_enabled, - metadata["version"] as app_version - FROM stream` - - err := ssql.Execute(rsql) - if err != nil { - fmt.Printf("❌ SQL执行失败: %v\n", err) - return - } - - // 准备测试数据 - testData := []map[string]interface{}{ - { - "device_id": "gateway-001", - "config": map[string]interface{}{ - "host": "192.168.1.100", - "port": 8080, - "protocol": "https", - }, - "settings": map[string]interface{}{ - "enable_ssl": true, - "timeout": 30, - "max_retries": 3, - }, - "metadata": map[string]interface{}{ - "version": "v2.1.3", - "build_date": "2023-12-01", - "vendor": "TechCorp", - }, - }, - { - "device_id": "gateway-002", - "config": map[string]interface{}{ - "host": "192.168.1.101", - "port": 8443, - "protocol": "https", - }, - "settings": map[string]interface{}{ - "enable_ssl": false, - "timeout": 60, - "max_retries": 5, - }, - "metadata": map[string]interface{}{ - "version": "v2.0.8", - "build_date": "2023-11-15", - "vendor": "TechCorp", - }, - }, - } - - var wg sync.WaitGroup - wg.Add(1) - - // 设置结果回调 - ssql.Stream().AddSink(func(result interface{}) { - defer wg.Done() - - fmt.Println(" 🗝️ Map键访问结果:") - if resultSlice, ok := result.([]map[string]interface{}); ok { - for i, item := range resultSlice { - fmt.Printf(" 记录 %d:\n", i+1) - fmt.Printf(" 设备ID: %v\n", item["device_id"]) - fmt.Printf(" 服务器主机: %v\n", item["server_host"]) - fmt.Printf(" 服务器端口: %v\n", item["server_port"]) - fmt.Printf(" SSL启用: %v\n", item["ssl_enabled"]) - fmt.Printf(" 应用版本: %v\n", item["app_version"]) - fmt.Println() - } - } - }) - - // 添加测试数据 - for _, data := range testData { - ssql.Stream().AddData(data) - } - - // 等待结果 - wg.Wait() -} - -// 演示混合复杂访问 -func demonstrateComplexMixedAccess(ssql *streamsql.Streamsql) { - // SQL查询:混合使用数组索引、Map键和嵌套字段访问 - rsql := `SELECT building, - floors[0].rooms[2]['name'] as first_floor_room3_name, - floors[1].sensors[0].readings['temperature'] as second_floor_first_sensor_temp, - metadata.building_info['architect'] as building_architect, - alerts[-1].message as latest_alert - FROM stream` - - err := ssql.Execute(rsql) - if err != nil { - fmt.Printf("❌ SQL执行失败: %v\n", err) - return - } - - // 准备复杂嵌套数据 - testData := map[string]interface{}{ - "building": "智能大厦A座", - "floors": []interface{}{ - // 第一层 - map[string]interface{}{ - "floor_number": 1, - "rooms": []interface{}{ - map[string]interface{}{"name": "大厅", "type": "public"}, - map[string]interface{}{"name": "接待室", "type": "office"}, - map[string]interface{}{"name": "会议室A", "type": "meeting"}, - map[string]interface{}{"name": "休息区", "type": "lounge"}, - }, - }, - // 第二层 - map[string]interface{}{ - "floor_number": 2, - "sensors": []interface{}{ - map[string]interface{}{ - "id": "sensor-201", - "readings": map[string]interface{}{ - "temperature": 23.5, - "humidity": 58.2, - "co2": 420, - }, - }, - map[string]interface{}{ - "id": "sensor-202", - "readings": map[string]interface{}{ - "temperature": 24.1, - "humidity": 60.8, - "co2": 380, - }, - }, - }, - }, - }, - "metadata": map[string]interface{}{ - "building_info": map[string]interface{}{ - "architect": "张建筑师", - "year_built": 2020, - "total_floors": 25, - }, - "owner": "科技园管委会", - }, - "alerts": []interface{}{ - map[string]interface{}{"level": "info", "message": "系统启动完成"}, - map[string]interface{}{"level": "warning", "message": "传感器信号弱"}, - map[string]interface{}{"level": "info", "message": "定期维护提醒"}, - }, - } - - var wg sync.WaitGroup - wg.Add(1) - - // 设置结果回调 - ssql.Stream().AddSink(func(result interface{}) { - defer wg.Done() - - fmt.Println(" 🔄 混合复杂访问结果:") - if resultSlice, ok := result.([]map[string]interface{}); ok { - for i, item := range resultSlice { - fmt.Printf(" 记录 %d:\n", i+1) - fmt.Printf(" 建筑: %v\n", item["building"]) - fmt.Printf(" 一层第3个房间: %v\n", item["first_floor_room3_name"]) - fmt.Printf(" 二层第1个传感器温度: %v°C\n", item["second_floor_first_sensor_temp"]) - fmt.Printf(" 建筑师: %v\n", item["building_architect"]) - fmt.Printf(" 最新警报: %v\n", item["latest_alert"]) - fmt.Println() - } - } - }) - - // 添加数据 - ssql.Stream().AddData(testData) - - // 等待结果 - wg.Wait() -} - -// 演示负数索引访问 -func demonstrateNegativeIndexAccess(ssql *streamsql.Streamsql) { - // SQL查询:使用负数索引访问数组末尾元素 - rsql := `SELECT device_name, - readings[-1] as latest_reading, - history[-2] as second_last_event, - tags[-1] as last_tag - FROM stream` - - err := ssql.Execute(rsql) - if err != nil { - fmt.Printf("❌ SQL执行失败: %v\n", err) - return - } - - // 准备测试数据 - testData := []map[string]interface{}{ - { - "device_name": "温度监测器-Alpha", - "readings": []interface{}{18.5, 19.2, 20.1, 21.3, 22.8, 23.5}, // [-1] = 23.5 - "history": []interface{}{"boot", "calibration", "running", "alert", "resolved"}, // [-2] = "alert" - "tags": []interface{}{"indoor", "critical", "monitored"}, // [-1] = "monitored" - }, - { - "device_name": "湿度传感器-Beta", - "readings": []interface{}{45.2, 47.8, 52.1, 48.9}, // [-1] = 48.9 - "history": []interface{}{"init", "testing", "deployed"}, // [-2] = "testing" - "tags": []interface{}{"outdoor", "backup"}, // [-1] = "backup" - }, - } - - var wg sync.WaitGroup - wg.Add(1) - - // 设置结果回调 - ssql.Stream().AddSink(func(result interface{}) { - defer wg.Done() - - fmt.Println(" ⬅️ 负数索引访问结果:") - if resultSlice, ok := result.([]map[string]interface{}); ok { - for i, item := range resultSlice { - fmt.Printf(" 记录 %d:\n", i+1) - fmt.Printf(" 设备名称: %v\n", item["device_name"]) - fmt.Printf(" 最新读数: %v\n", item["latest_reading"]) - fmt.Printf(" 倒数第二个事件: %v\n", item["second_last_event"]) - fmt.Printf(" 最后一个标签: %v\n", item["last_tag"]) - fmt.Println() - } - } - }) - - // 添加测试数据 - for _, data := range testData { - ssql.Stream().AddData(data) - } - - // 等待结果 - wg.Wait() -} - -// 演示数组索引聚合计算 -func demonstrateArrayIndexAggregation(ssql *streamsql.Streamsql) { - // SQL查询:对数组中特定位置的数据进行聚合计算 - rsql := `SELECT location, - AVG(sensors[0].temperature) as avg_first_sensor_temp, - MAX(sensors[1].humidity) as max_second_sensor_humidity, - COUNT(*) as device_count - FROM stream - GROUP BY location, TumblingWindow('2s') - WITH (TIMESTAMP='timestamp', TIMEUNIT='ss')` - - err := ssql.Execute(rsql) - if err != nil { - fmt.Printf("❌ SQL执行失败: %v\n", err) - return - } - - var resultCount int - var wg sync.WaitGroup - wg.Add(1) - - // 设置结果回调 - ssql.Stream().AddSink(func(result interface{}) { - defer wg.Done() - - fmt.Println(" 📈 数组索引聚合计算结果:") - if resultSlice, ok := result.([]map[string]interface{}); ok { - for i, item := range resultSlice { - resultCount++ - fmt.Printf(" 聚合结果 %d:\n", i+1) - fmt.Printf(" 位置: %v\n", item["location"]) - fmt.Printf(" 第一个传感器平均温度: %.2f°C\n", item["avg_first_sensor_temp"]) - fmt.Printf(" 第二个传感器最大湿度: %.1f%%\n", item["max_second_sensor_humidity"]) - fmt.Printf(" 设备数量: %v\n", item["device_count"]) - fmt.Println() - } - } - }) - - // 生成模拟数据 - locations := []string{"车间A", "车间B", "车间C"} - - go func() { - for i := 0; i < 12; i++ { - location := locations[rand.Intn(len(locations))] - - data := map[string]interface{}{ - "device_id": fmt.Sprintf("device-%03d", i+1), - "location": location, - "sensors": []interface{}{ - map[string]interface{}{ - "temperature": 20.0 + rand.Float64()*10.0, // 20-30°C - "humidity": 50.0 + rand.Float64()*20.0, // 50-70% - }, - map[string]interface{}{ - "temperature": 18.0 + rand.Float64()*12.0, // 18-30°C - "humidity": 45.0 + rand.Float64()*25.0, // 45-70% - }, - }, - "timestamp": time.Now().Unix(), - } - - ssql.Stream().AddData(data) - time.Sleep(200 * time.Millisecond) // 每200ms发送一条数据 - } - }() - - // 等待聚合结果 - ctx, cancel := context.WithTimeout(context.Background(), 8*time.Second) - defer cancel() - - select { - case <-ctx.Done(): - fmt.Println(" ⏰ 聚合计算超时") - case <-func() chan struct{} { - done := make(chan struct{}) - go func() { - wg.Wait() - close(done) - }() - return done - }(): - fmt.Printf(" ✅ 聚合计算完成,共生成 %d 个窗口结果\n", resultCount) - } -} +package main + +import ( + "context" + "fmt" + "math/rand" + "sync" + "time" + + "github.com/rulego/streamsql" +) + +func main() { + fmt.Println("🔧 StreamSQL 复杂嵌套字段访问功能演示") + fmt.Println("=======================================") + + // 创建 StreamSQL 实例 + ssql := streamsql.New() + defer ssql.Stop() + + // 演示1: 数组索引访问 + fmt.Println("\n📊 演示1: 数组索引访问") + demonstrateArrayAccess(ssql) + + // 演示2: Map键访问 + fmt.Println("\n🗝️ 演示2: Map键访问") + demonstrateMapKeyAccess(ssql) + + // 演示3: 混合复杂访问 + fmt.Println("\n🔄 演示3: 混合复杂访问") + demonstrateComplexMixedAccess(ssql) + + // 演示4: 负数索引访问 + fmt.Println("\n⬅️ 演示4: 负数索引访问") + demonstrateNegativeIndexAccess(ssql) + + // 演示5: 数组索引聚合计算 + fmt.Println("\n📈 演示5: 数组索引聚合计算") + demonstrateArrayIndexAggregation(ssql) + + fmt.Println("\n✅ 演示完成!") +} + +// 演示数组索引访问 +func demonstrateArrayAccess(ssql *streamsql.Streamsql) { + // SQL查询:提取数组中的特定元素 + rsql := `SELECT device, + sensors[0].temperature as first_sensor_temp, + sensors[1].humidity as second_sensor_humidity, + data[2] as third_data_item + FROM stream` + + err := ssql.Execute(rsql) + if err != nil { + fmt.Printf("❌ SQL执行失败: %v\n", err) + return + } + + // 准备测试数据 + testData := []map[string]interface{}{ + { + "device": "工业传感器-001", + "sensors": []interface{}{ + map[string]interface{}{"temperature": 25.5, "humidity": 60.2}, + map[string]interface{}{"temperature": 26.8, "humidity": 58.7}, + map[string]interface{}{"temperature": 24.1, "humidity": 62.1}, + }, + "data": []interface{}{"status_ok", "battery_95%", "signal_strong", "location_A1"}, + "timestamp": time.Now().Unix(), + }, + { + "device": "环境监测器-002", + "sensors": []interface{}{ + map[string]interface{}{"temperature": 22.3, "humidity": 65.8}, + map[string]interface{}{"temperature": 23.1, "humidity": 63.2}, + }, + "data": []interface{}{"status_warning", "battery_78%", "signal_weak"}, + "timestamp": time.Now().Unix(), + }, + } + + var wg sync.WaitGroup + wg.Add(1) + + // 设置结果回调 + ssql.AddSink(func(result interface{}) { + defer wg.Done() + + fmt.Println(" 📋 数组索引访问结果:") + if resultSlice, ok := result.([]map[string]interface{}); ok { + for i, item := range resultSlice { + fmt.Printf(" 记录 %d:\n", i+1) + fmt.Printf(" 设备: %v\n", item["device"]) + fmt.Printf(" 第一个传感器温度: %v°C\n", item["first_sensor_temp"]) + fmt.Printf(" 第二个传感器湿度: %v%%\n", item["second_sensor_humidity"]) + fmt.Printf(" 第三个数据项: %v\n", item["third_data_item"]) + fmt.Println() + } + } + }) + + // 添加测试数据 + for _, data := range testData { + ssql.Emit(data) + } + + // 等待结果 + wg.Wait() +} + +// 演示Map键访问 +func demonstrateMapKeyAccess(ssql *streamsql.Streamsql) { + // SQL查询:使用字符串键访问Map数据 + rsql := `SELECT device_id, + config['host'] as server_host, + config["port"] as server_port, + settings['enable_ssl'] as ssl_enabled, + metadata["version"] as app_version + FROM stream` + + err := ssql.Execute(rsql) + if err != nil { + fmt.Printf("❌ SQL执行失败: %v\n", err) + return + } + + // 准备测试数据 + testData := []map[string]interface{}{ + { + "device_id": "gateway-001", + "config": map[string]interface{}{ + "host": "192.168.1.100", + "port": 8080, + "protocol": "https", + }, + "settings": map[string]interface{}{ + "enable_ssl": true, + "timeout": 30, + "max_retries": 3, + }, + "metadata": map[string]interface{}{ + "version": "v2.1.3", + "build_date": "2023-12-01", + "vendor": "TechCorp", + }, + }, + { + "device_id": "gateway-002", + "config": map[string]interface{}{ + "host": "192.168.1.101", + "port": 8443, + "protocol": "https", + }, + "settings": map[string]interface{}{ + "enable_ssl": false, + "timeout": 60, + "max_retries": 5, + }, + "metadata": map[string]interface{}{ + "version": "v2.0.8", + "build_date": "2023-11-15", + "vendor": "TechCorp", + }, + }, + } + + var wg sync.WaitGroup + wg.Add(1) + + // 设置结果回调 + ssql.AddSink(func(result interface{}) { + defer wg.Done() + + fmt.Println(" 🗝️ Map键访问结果:") + if resultSlice, ok := result.([]map[string]interface{}); ok { + for i, item := range resultSlice { + fmt.Printf(" 记录 %d:\n", i+1) + fmt.Printf(" 设备ID: %v\n", item["device_id"]) + fmt.Printf(" 服务器主机: %v\n", item["server_host"]) + fmt.Printf(" 服务器端口: %v\n", item["server_port"]) + fmt.Printf(" SSL启用: %v\n", item["ssl_enabled"]) + fmt.Printf(" 应用版本: %v\n", item["app_version"]) + fmt.Println() + } + } + }) + + // 添加测试数据 + for _, data := range testData { + ssql.Emit(data) + } + + // 等待结果 + wg.Wait() +} + +// 演示混合复杂访问 +func demonstrateComplexMixedAccess(ssql *streamsql.Streamsql) { + // SQL查询:混合使用数组索引、Map键和嵌套字段访问 + rsql := `SELECT building, + floors[0].rooms[2]['name'] as first_floor_room3_name, + floors[1].sensors[0].readings['temperature'] as second_floor_first_sensor_temp, + metadata.building_info['architect'] as building_architect, + alerts[-1].message as latest_alert + FROM stream` + + err := ssql.Execute(rsql) + if err != nil { + fmt.Printf("❌ SQL执行失败: %v\n", err) + return + } + + // 准备复杂嵌套数据 + testData := map[string]interface{}{ + "building": "智能大厦A座", + "floors": []interface{}{ + // 第一层 + map[string]interface{}{ + "floor_number": 1, + "rooms": []interface{}{ + map[string]interface{}{"name": "大厅", "type": "public"}, + map[string]interface{}{"name": "接待室", "type": "office"}, + map[string]interface{}{"name": "会议室A", "type": "meeting"}, + map[string]interface{}{"name": "休息区", "type": "lounge"}, + }, + }, + // 第二层 + map[string]interface{}{ + "floor_number": 2, + "sensors": []interface{}{ + map[string]interface{}{ + "id": "sensor-201", + "readings": map[string]interface{}{ + "temperature": 23.5, + "humidity": 58.2, + "co2": 420, + }, + }, + map[string]interface{}{ + "id": "sensor-202", + "readings": map[string]interface{}{ + "temperature": 24.1, + "humidity": 60.8, + "co2": 380, + }, + }, + }, + }, + }, + "metadata": map[string]interface{}{ + "building_info": map[string]interface{}{ + "architect": "张建筑师", + "year_built": 2020, + "total_floors": 25, + }, + "owner": "科技园管委会", + }, + "alerts": []interface{}{ + map[string]interface{}{"level": "info", "message": "系统启动完成"}, + map[string]interface{}{"level": "warning", "message": "传感器信号弱"}, + map[string]interface{}{"level": "info", "message": "定期维护提醒"}, + }, + } + + var wg sync.WaitGroup + wg.Add(1) + + // 设置结果回调 + ssql.AddSink(func(result interface{}) { + defer wg.Done() + + fmt.Println(" 🔄 混合复杂访问结果:") + if resultSlice, ok := result.([]map[string]interface{}); ok { + for i, item := range resultSlice { + fmt.Printf(" 记录 %d:\n", i+1) + fmt.Printf(" 建筑: %v\n", item["building"]) + fmt.Printf(" 一层第3个房间: %v\n", item["first_floor_room3_name"]) + fmt.Printf(" 二层第1个传感器温度: %v°C\n", item["second_floor_first_sensor_temp"]) + fmt.Printf(" 建筑师: %v\n", item["building_architect"]) + fmt.Printf(" 最新警报: %v\n", item["latest_alert"]) + fmt.Println() + } + } + }) + + // 添加数据 + ssql.Emit(testData) + + // 等待结果 + wg.Wait() +} + +// 演示负数索引访问 +func demonstrateNegativeIndexAccess(ssql *streamsql.Streamsql) { + // SQL查询:使用负数索引访问数组末尾元素 + rsql := `SELECT device_name, + readings[-1] as latest_reading, + history[-2] as second_last_event, + tags[-1] as last_tag + FROM stream` + + err := ssql.Execute(rsql) + if err != nil { + fmt.Printf("❌ SQL执行失败: %v\n", err) + return + } + + // 准备测试数据 + testData := []map[string]interface{}{ + { + "device_name": "温度监测器-Alpha", + "readings": []interface{}{18.5, 19.2, 20.1, 21.3, 22.8, 23.5}, // [-1] = 23.5 + "history": []interface{}{"boot", "calibration", "running", "alert", "resolved"}, // [-2] = "alert" + "tags": []interface{}{"indoor", "critical", "monitored"}, // [-1] = "monitored" + }, + { + "device_name": "湿度传感器-Beta", + "readings": []interface{}{45.2, 47.8, 52.1, 48.9}, // [-1] = 48.9 + "history": []interface{}{"init", "testing", "deployed"}, // [-2] = "testing" + "tags": []interface{}{"outdoor", "backup"}, // [-1] = "backup" + }, + } + + var wg sync.WaitGroup + wg.Add(1) + + // 设置结果回调 + ssql.AddSink(func(result interface{}) { + defer wg.Done() + + fmt.Println(" ⬅️ 负数索引访问结果:") + if resultSlice, ok := result.([]map[string]interface{}); ok { + for i, item := range resultSlice { + fmt.Printf(" 记录 %d:\n", i+1) + fmt.Printf(" 设备名称: %v\n", item["device_name"]) + fmt.Printf(" 最新读数: %v\n", item["latest_reading"]) + fmt.Printf(" 倒数第二个事件: %v\n", item["second_last_event"]) + fmt.Printf(" 最后一个标签: %v\n", item["last_tag"]) + fmt.Println() + } + } + }) + + // 添加测试数据 + for _, data := range testData { + ssql.Emit(data) + } + + // 等待结果 + wg.Wait() +} + +// 演示数组索引聚合计算 +func demonstrateArrayIndexAggregation(ssql *streamsql.Streamsql) { + // SQL查询:对数组中特定位置的数据进行聚合计算 + rsql := `SELECT location, + AVG(sensors[0].temperature) as avg_first_sensor_temp, + MAX(sensors[1].humidity) as max_second_sensor_humidity, + COUNT(*) as device_count + FROM stream + GROUP BY location, TumblingWindow('2s') + WITH (TIMESTAMP='timestamp', TIMEUNIT='ss')` + + err := ssql.Execute(rsql) + if err != nil { + fmt.Printf("❌ SQL执行失败: %v\n", err) + return + } + + var resultCount int + var wg sync.WaitGroup + wg.Add(1) + + // 设置结果回调 + ssql.AddSink(func(result interface{}) { + defer wg.Done() + + fmt.Println(" 📈 数组索引聚合计算结果:") + if resultSlice, ok := result.([]map[string]interface{}); ok { + for i, item := range resultSlice { + resultCount++ + fmt.Printf(" 聚合结果 %d:\n", i+1) + fmt.Printf(" 位置: %v\n", item["location"]) + fmt.Printf(" 第一个传感器平均温度: %.2f°C\n", item["avg_first_sensor_temp"]) + fmt.Printf(" 第二个传感器最大湿度: %.1f%%\n", item["max_second_sensor_humidity"]) + fmt.Printf(" 设备数量: %v\n", item["device_count"]) + fmt.Println() + } + } + }) + + // 生成模拟数据 + locations := []string{"车间A", "车间B", "车间C"} + + go func() { + for i := 0; i < 12; i++ { + location := locations[rand.Intn(len(locations))] + + data := map[string]interface{}{ + "device_id": fmt.Sprintf("device-%03d", i+1), + "location": location, + "sensors": []interface{}{ + map[string]interface{}{ + "temperature": 20.0 + rand.Float64()*10.0, // 20-30°C + "humidity": 50.0 + rand.Float64()*20.0, // 50-70% + }, + map[string]interface{}{ + "temperature": 18.0 + rand.Float64()*12.0, // 18-30°C + "humidity": 45.0 + rand.Float64()*25.0, // 45-70% + }, + }, + "timestamp": time.Now().Unix(), + } + + ssql.Emit(data) + time.Sleep(200 * time.Millisecond) // 每200ms发送一条数据 + } + }() + + // 等待聚合结果 + ctx, cancel := context.WithTimeout(context.Background(), 8*time.Second) + defer cancel() + + select { + case <-ctx.Done(): + fmt.Println(" ⏰ 聚合计算超时") + case <-func() chan struct{} { + done := make(chan struct{}) + go func() { + wg.Wait() + close(done) + }() + return done + }(): + fmt.Printf(" ✅ 聚合计算完成,共生成 %d 个窗口结果\n", resultCount) + } +} diff --git a/examples/comprehensive-test/main.go b/examples/comprehensive-test/main.go index 2c02fb2..1949404 100644 --- a/examples/comprehensive-test/main.go +++ b/examples/comprehensive-test/main.go @@ -1,428 +1,428 @@ -package main - -import ( - "fmt" - "math" - "math/rand" - "time" - - "github.com/rulego/streamsql" - "github.com/rulego/streamsql/functions" - "github.com/rulego/streamsql/utils/cast" -) - -func main() { - fmt.Println("🚀 StreamSQL 综合测试演示") - fmt.Println("=============================") - - // 注册自定义函数 - registerCustomFunctions() - - // 运行各种测试场景 - runAllTests() - - fmt.Println("\n✅ 所有测试完成!") -} - -// 注册自定义函数 -func registerCustomFunctions() { - fmt.Println("\n📋 注册自定义函数...") - - // 数学函数:平方 - err := functions.RegisterCustomFunction( - "square", - functions.TypeMath, - "数学函数", - "计算平方", - 1, 1, - func(ctx *functions.FunctionContext, args []interface{}) (interface{}, error) { - val := cast.ToFloat64(args[0]) - return val * val, nil - }, - ) - if err != nil { - fmt.Printf("❌ 注册square函数失败: %v\n", err) - } else { - fmt.Println(" ✓ 注册数学函数: square") - } - - // 华氏度转摄氏度函数 - err = functions.RegisterCustomFunction( - "f_to_c", - functions.TypeConversion, - "温度转换", - "华氏度转摄氏度", - 1, 1, - func(ctx *functions.FunctionContext, args []interface{}) (interface{}, error) { - fahrenheit := cast.ToFloat64(args[0]) - celsius := (fahrenheit - 32) * 5 / 9 - return celsius, nil - }, - ) - if err != nil { - fmt.Printf("❌ 注册f_to_c函数失败: %v\n", err) - } else { - fmt.Println(" ✓ 注册转换函数: f_to_c") - } - - // 圆面积计算函数 - err = functions.RegisterCustomFunction( - "circle_area", - functions.TypeMath, - "几何计算", - "计算圆的面积", - 1, 1, - func(ctx *functions.FunctionContext, args []interface{}) (interface{}, error) { - radius := cast.ToFloat64(args[0]) - if radius < 0 { - return nil, fmt.Errorf("半径必须为正数") - } - area := math.Pi * radius * radius - return area, nil - }, - ) - if err != nil { - fmt.Printf("❌ 注册circle_area函数失败: %v\n", err) - } else { - fmt.Println(" ✓ 注册几何函数: circle_area") - } -} - -// 运行所有测试 -func runAllTests() { - // 测试1:基础数据过滤 - testBasicFiltering() - - // 测试2:聚合分析 - testAggregation() - - // 测试3:滑动窗口 - testSlidingWindow() - - // 测试4:嵌套字段访问 - testNestedFields() - - // 测试5:自定义函数 - testCustomFunctions() - - // 测试6:复杂查询 - testComplexQuery() -} - -// 测试1:基础数据过滤 -func testBasicFiltering() { - fmt.Println("\n🔍 测试1:基础数据过滤") - fmt.Println("========================") - - ssql := streamsql.New() - defer ssql.Stop() - - // 过滤温度大于25度的数据 - sql := "SELECT deviceId, temperature FROM stream WHERE temperature > 25" - - err := ssql.Execute(sql) - if err != nil { - fmt.Printf("❌ SQL执行失败: %v\n", err) - return - } - - // 添加结果处理函数 - ssql.Stream().AddSink(func(result interface{}) { - fmt.Printf(" 📊 高温告警: %v\n", result) - }) - - // 发送测试数据 - testData := []map[string]interface{}{ - {"deviceId": "sensor001", "temperature": 23.5}, // 不会触发告警 - {"deviceId": "sensor002", "temperature": 28.3}, // 会触发告警 - {"deviceId": "sensor003", "temperature": 31.2}, // 会触发告警 - {"deviceId": "sensor004", "temperature": 22.1}, // 不会触发告警 - } - - for _, data := range testData { - ssql.AddData(data) - time.Sleep(100 * time.Millisecond) - } - - time.Sleep(500 * time.Millisecond) - fmt.Println(" ✅ 基础过滤测试完成") -} - -// 测试2:聚合分析 -func testAggregation() { - fmt.Println("\n📈 测试2:聚合分析") - fmt.Println("==================") - - ssql := streamsql.New() - defer ssql.Stop() - - // 每2秒计算一次各设备的平均温度 - sql := `SELECT deviceId, - AVG(temperature) as avg_temp, - COUNT(*) as sample_count, - MAX(temperature) as max_temp, - MIN(temperature) as min_temp - FROM stream - GROUP BY deviceId, TumblingWindow('2s')` - - err := ssql.Execute(sql) - if err != nil { - fmt.Printf("❌ SQL执行失败: %v\n", err) - return - } - - // 处理聚合结果 - ssql.Stream().AddSink(func(result interface{}) { - fmt.Printf(" 📊 聚合结果: %v\n", result) - }) - - // 模拟传感器数据流 - devices := []string{"sensor001", "sensor002", "sensor003"} - for i := 0; i < 8; i++ { - for _, device := range devices { - data := map[string]interface{}{ - "deviceId": device, - "temperature": 20.0 + rand.Float64()*15, // 20-35度随机温度 - "timestamp": time.Now(), - } - ssql.AddData(data) - } - time.Sleep(300 * time.Millisecond) - } - - // 等待窗口触发 - time.Sleep(2 * time.Second) - ssql.Stream().Window.Trigger() - time.Sleep(500 * time.Millisecond) - fmt.Println(" ✅ 聚合分析测试完成") -} - -// 测试3:滑动窗口 -func testSlidingWindow() { - fmt.Println("\n🔄 测试3:滑动窗口") - fmt.Println("==================") - - ssql := streamsql.New() - defer ssql.Stop() - - // 6秒滑动窗口,每2秒滑动一次 - sql := `SELECT deviceId, - AVG(temperature) as avg_temp, - MAX(temperature) as max_temp, - MIN(temperature) as min_temp, - COUNT(*) as count - FROM stream - WHERE temperature > 0 - GROUP BY deviceId, SlidingWindow('6s', '2s')` - - err := ssql.Execute(sql) - if err != nil { - fmt.Printf("❌ SQL执行失败: %v\n", err) - return - } - - ssql.Stream().AddSink(func(result interface{}) { - fmt.Printf(" 📊 滑动窗口分析: %v\n", result) - }) - - // 持续发送数据 - for i := 0; i < 10; i++ { - data := map[string]interface{}{ - "deviceId": "sensor001", - "temperature": 20.0 + rand.Float64()*10, - "timestamp": time.Now(), - } - ssql.AddData(data) - time.Sleep(800 * time.Millisecond) - } - - time.Sleep(1 * time.Second) - fmt.Println(" ✅ 滑动窗口测试完成") -} - -// 测试4:嵌套字段访问 -func testNestedFields() { - fmt.Println("\n🔧 测试4:嵌套字段访问") - fmt.Println("=======================") - - ssql := streamsql.New() - defer ssql.Stop() - - // 访问嵌套字段的SQL查询 - sql := `SELECT device.info.name as device_name, - device.location.building as building, - sensor.temperature as temp, - UPPER(device.info.type) as device_type - FROM stream - WHERE sensor.temperature > 25 AND device.info.status = 'active'` - - err := ssql.Execute(sql) - if err != nil { - fmt.Printf("❌ SQL执行失败: %v\n", err) - return - } - - ssql.Stream().AddSink(func(result interface{}) { - fmt.Printf(" 📊 嵌套字段结果: %v\n", result) - }) - - // 发送嵌套结构数据 - complexData := []map[string]interface{}{ - { - "device": map[string]interface{}{ - "info": map[string]interface{}{ - "name": "温度传感器001", - "type": "temperature", - "status": "active", - }, - "location": map[string]interface{}{ - "building": "A栋", - "floor": "3F", - }, - }, - "sensor": map[string]interface{}{ - "temperature": 28.5, - "humidity": 65.0, - }, - }, - { - "device": map[string]interface{}{ - "info": map[string]interface{}{ - "name": "湿度传感器002", - "type": "humidity", - "status": "inactive", // 不会匹配 - }, - "location": map[string]interface{}{ - "building": "B栋", - "floor": "2F", - }, - }, - "sensor": map[string]interface{}{ - "temperature": 30.0, - "humidity": 70.0, - }, - }, - } - - for _, data := range complexData { - ssql.AddData(data) - time.Sleep(200 * time.Millisecond) - } - - time.Sleep(500 * time.Millisecond) - fmt.Println(" ✅ 嵌套字段测试完成") -} - -// 测试5:自定义函数 -func testCustomFunctions() { - fmt.Println("\n🎯 测试5:自定义函数") - fmt.Println("====================") - - ssql := streamsql.New() - defer ssql.Stop() - - // 使用自定义函数的SQL查询 - sql := `SELECT - device, - square(value) as squared_value, - f_to_c(temperature) as celsius, - circle_area(radius) as area - FROM stream - WHERE value > 0` - - err := ssql.Execute(sql) - if err != nil { - fmt.Printf("❌ SQL执行失败: %v\n", err) - return - } - - ssql.Stream().AddSink(func(result interface{}) { - fmt.Printf(" 📊 自定义函数结果: %v\n", result) - }) - - // 添加测试数据 - testData := []map[string]interface{}{ - { - "device": "sensor1", - "value": 5.0, - "temperature": 68.0, // 华氏度 - "radius": 3.0, - }, - { - "device": "sensor2", - "value": 10.0, - "temperature": 86.0, // 华氏度 - "radius": 2.5, - }, - { - "device": "sensor3", - "value": 0.0, // 不会匹配WHERE条件 - "temperature": 32.0, - "radius": 1.0, - }, - } - - for _, data := range testData { - ssql.AddData(data) - time.Sleep(200 * time.Millisecond) - } - - time.Sleep(500 * time.Millisecond) - fmt.Println(" ✅ 自定义函数测试完成") -} - -// 测试6:复杂查询 -func testComplexQuery() { - fmt.Println("\n🔬 测试6:复杂查询") - fmt.Println("==================") - - ssql := streamsql.New() - defer ssql.Stop() - - // 复杂的聚合查询,结合自定义函数和嵌套字段 - sql := `SELECT - device.location as location, - AVG(square(sensor.temperature)) as avg_temp_squared, - MAX(f_to_c(sensor.temperature)) as max_celsius, - COUNT(*) as sample_count, - SUM(circle_area(device.radius)) as total_area - FROM stream - WHERE sensor.temperature > 20 AND device.status = 'online' - GROUP BY device.location, TumblingWindow('3s')` - - err := ssql.Execute(sql) - if err != nil { - fmt.Printf("❌ SQL执行失败: %v\n", err) - return - } - - ssql.Stream().AddSink(func(result interface{}) { - fmt.Printf(" 📊 复杂查询结果: %v\n", result) - }) - - // 发送复杂测试数据 - locations := []string{"room-A", "room-B", "room-C"} - for i := 0; i < 12; i++ { - location := locations[i%len(locations)] - data := map[string]interface{}{ - "device": map[string]interface{}{ - "location": location, - "status": "online", - "radius": 1.0 + rand.Float64()*2.0, // 1-3的随机半径 - }, - "sensor": map[string]interface{}{ - "temperature": 25.0 + rand.Float64()*10.0, // 25-35度 - "humidity": 50.0 + rand.Float64()*30.0, // 50-80% - }, - "timestamp": time.Now(), - } - ssql.AddData(data) - time.Sleep(300 * time.Millisecond) - } - - // 等待窗口触发 - time.Sleep(3 * time.Second) - ssql.Stream().Window.Trigger() - time.Sleep(500 * time.Millisecond) - fmt.Println(" ✅ 复杂查询测试完成") -} \ No newline at end of file +package main + +import ( + "fmt" + "math" + "math/rand" + "time" + + "github.com/rulego/streamsql" + "github.com/rulego/streamsql/functions" + "github.com/rulego/streamsql/utils/cast" +) + +func main() { + fmt.Println("🚀 StreamSQL 综合测试演示") + fmt.Println("=============================") + + // 注册自定义函数 + registerCustomFunctions() + + // 运行各种测试场景 + runAllTests() + + fmt.Println("\n✅ 所有测试完成!") +} + +// 注册自定义函数 +func registerCustomFunctions() { + fmt.Println("\n📋 注册自定义函数...") + + // 数学函数:平方 + err := functions.RegisterCustomFunction( + "square", + functions.TypeMath, + "数学函数", + "计算平方", + 1, 1, + func(ctx *functions.FunctionContext, args []interface{}) (interface{}, error) { + val := cast.ToFloat64(args[0]) + return val * val, nil + }, + ) + if err != nil { + fmt.Printf("❌ 注册square函数失败: %v\n", err) + } else { + fmt.Println(" ✓ 注册数学函数: square") + } + + // 华氏度转摄氏度函数 + err = functions.RegisterCustomFunction( + "f_to_c", + functions.TypeConversion, + "温度转换", + "华氏度转摄氏度", + 1, 1, + func(ctx *functions.FunctionContext, args []interface{}) (interface{}, error) { + fahrenheit := cast.ToFloat64(args[0]) + celsius := (fahrenheit - 32) * 5 / 9 + return celsius, nil + }, + ) + if err != nil { + fmt.Printf("❌ 注册f_to_c函数失败: %v\n", err) + } else { + fmt.Println(" ✓ 注册转换函数: f_to_c") + } + + // 圆面积计算函数 + err = functions.RegisterCustomFunction( + "circle_area", + functions.TypeMath, + "几何计算", + "计算圆的面积", + 1, 1, + func(ctx *functions.FunctionContext, args []interface{}) (interface{}, error) { + radius := cast.ToFloat64(args[0]) + if radius < 0 { + return nil, fmt.Errorf("半径必须为正数") + } + area := math.Pi * radius * radius + return area, nil + }, + ) + if err != nil { + fmt.Printf("❌ 注册circle_area函数失败: %v\n", err) + } else { + fmt.Println(" ✓ 注册几何函数: circle_area") + } +} + +// 运行所有测试 +func runAllTests() { + // 测试1:基础数据过滤 + testBasicFiltering() + + // 测试2:聚合分析 + testAggregation() + + // 测试3:滑动窗口 + testSlidingWindow() + + // 测试4:嵌套字段访问 + testNestedFields() + + // 测试5:自定义函数 + testCustomFunctions() + + // 测试6:复杂查询 + testComplexQuery() +} + +// 测试1:基础数据过滤 +func testBasicFiltering() { + fmt.Println("\n🔍 测试1:基础数据过滤") + fmt.Println("========================") + + ssql := streamsql.New() + defer ssql.Stop() + + // 过滤温度大于25度的数据 + sql := "SELECT deviceId, temperature FROM stream WHERE temperature > 25" + + err := ssql.Execute(sql) + if err != nil { + fmt.Printf("❌ SQL执行失败: %v\n", err) + return + } + + // 添加结果处理函数 + ssql.AddSink(func(result interface{}) { + fmt.Printf(" 📊 高温告警: %v\n", result) + }) + + // 发送测试数据 + testData := []map[string]interface{}{ + {"deviceId": "sensor001", "temperature": 23.5}, // 不会触发告警 + {"deviceId": "sensor002", "temperature": 28.3}, // 会触发告警 + {"deviceId": "sensor003", "temperature": 31.2}, // 会触发告警 + {"deviceId": "sensor004", "temperature": 22.1}, // 不会触发告警 + } + + for _, data := range testData { + ssql.Emit(data) + time.Sleep(100 * time.Millisecond) + } + + time.Sleep(500 * time.Millisecond) + fmt.Println(" ✅ 基础过滤测试完成") +} + +// 测试2:聚合分析 +func testAggregation() { + fmt.Println("\n📈 测试2:聚合分析") + fmt.Println("==================") + + ssql := streamsql.New() + defer ssql.Stop() + + // 每2秒计算一次各设备的平均温度 + sql := `SELECT deviceId, + AVG(temperature) as avg_temp, + COUNT(*) as sample_count, + MAX(temperature) as max_temp, + MIN(temperature) as min_temp + FROM stream + GROUP BY deviceId, TumblingWindow('2s')` + + err := ssql.Execute(sql) + if err != nil { + fmt.Printf("❌ SQL执行失败: %v\n", err) + return + } + + // 处理聚合结果 + ssql.AddSink(func(result interface{}) { + fmt.Printf(" 📊 聚合结果: %v\n", result) + }) + + // 模拟传感器数据流 + devices := []string{"sensor001", "sensor002", "sensor003"} + for i := 0; i < 8; i++ { + for _, device := range devices { + data := map[string]interface{}{ + "deviceId": device, + "temperature": 20.0 + rand.Float64()*15, // 20-35度随机温度 + "timestamp": time.Now(), + } + ssql.Emit(data) + } + time.Sleep(300 * time.Millisecond) + } + + // 等待窗口触发 + time.Sleep(2 * time.Second) + ssql.Stream().Window.Trigger() + time.Sleep(500 * time.Millisecond) + fmt.Println(" ✅ 聚合分析测试完成") +} + +// 测试3:滑动窗口 +func testSlidingWindow() { + fmt.Println("\n🔄 测试3:滑动窗口") + fmt.Println("==================") + + ssql := streamsql.New() + defer ssql.Stop() + + // 6秒滑动窗口,每2秒滑动一次 + sql := `SELECT deviceId, + AVG(temperature) as avg_temp, + MAX(temperature) as max_temp, + MIN(temperature) as min_temp, + COUNT(*) as count + FROM stream + WHERE temperature > 0 + GROUP BY deviceId, SlidingWindow('6s', '2s')` + + err := ssql.Execute(sql) + if err != nil { + fmt.Printf("❌ SQL执行失败: %v\n", err) + return + } + + ssql.AddSink(func(result interface{}) { + fmt.Printf(" 📊 滑动窗口分析: %v\n", result) + }) + + // 持续发送数据 + for i := 0; i < 10; i++ { + data := map[string]interface{}{ + "deviceId": "sensor001", + "temperature": 20.0 + rand.Float64()*10, + "timestamp": time.Now(), + } + ssql.Emit(data) + time.Sleep(800 * time.Millisecond) + } + + time.Sleep(1 * time.Second) + fmt.Println(" ✅ 滑动窗口测试完成") +} + +// 测试4:嵌套字段访问 +func testNestedFields() { + fmt.Println("\n🔧 测试4:嵌套字段访问") + fmt.Println("=======================") + + ssql := streamsql.New() + defer ssql.Stop() + + // 访问嵌套字段的SQL查询 + sql := `SELECT device.info.name as device_name, + device.location.building as building, + sensor.temperature as temp, + UPPER(device.info.type) as device_type + FROM stream + WHERE sensor.temperature > 25 AND device.info.status = 'active'` + + err := ssql.Execute(sql) + if err != nil { + fmt.Printf("❌ SQL执行失败: %v\n", err) + return + } + + ssql.AddSink(func(result interface{}) { + fmt.Printf(" 📊 嵌套字段结果: %v\n", result) + }) + + // 发送嵌套结构数据 + complexData := []map[string]interface{}{ + { + "device": map[string]interface{}{ + "info": map[string]interface{}{ + "name": "温度传感器001", + "type": "temperature", + "status": "active", + }, + "location": map[string]interface{}{ + "building": "A栋", + "floor": "3F", + }, + }, + "sensor": map[string]interface{}{ + "temperature": 28.5, + "humidity": 65.0, + }, + }, + { + "device": map[string]interface{}{ + "info": map[string]interface{}{ + "name": "湿度传感器002", + "type": "humidity", + "status": "inactive", // 不会匹配 + }, + "location": map[string]interface{}{ + "building": "B栋", + "floor": "2F", + }, + }, + "sensor": map[string]interface{}{ + "temperature": 30.0, + "humidity": 70.0, + }, + }, + } + + for _, data := range complexData { + ssql.Emit(data) + time.Sleep(200 * time.Millisecond) + } + + time.Sleep(500 * time.Millisecond) + fmt.Println(" ✅ 嵌套字段测试完成") +} + +// 测试5:自定义函数 +func testCustomFunctions() { + fmt.Println("\n🎯 测试5:自定义函数") + fmt.Println("====================") + + ssql := streamsql.New() + defer ssql.Stop() + + // 使用自定义函数的SQL查询 + sql := `SELECT + device, + square(value) as squared_value, + f_to_c(temperature) as celsius, + circle_area(radius) as area + FROM stream + WHERE value > 0` + + err := ssql.Execute(sql) + if err != nil { + fmt.Printf("❌ SQL执行失败: %v\n", err) + return + } + + ssql.AddSink(func(result interface{}) { + fmt.Printf(" 📊 自定义函数结果: %v\n", result) + }) + + // 添加测试数据 + testData := []map[string]interface{}{ + { + "device": "sensor1", + "value": 5.0, + "temperature": 68.0, // 华氏度 + "radius": 3.0, + }, + { + "device": "sensor2", + "value": 10.0, + "temperature": 86.0, // 华氏度 + "radius": 2.5, + }, + { + "device": "sensor3", + "value": 0.0, // 不会匹配WHERE条件 + "temperature": 32.0, + "radius": 1.0, + }, + } + + for _, data := range testData { + ssql.Emit(data) + time.Sleep(200 * time.Millisecond) + } + + time.Sleep(500 * time.Millisecond) + fmt.Println(" ✅ 自定义函数测试完成") +} + +// 测试6:复杂查询 +func testComplexQuery() { + fmt.Println("\n🔬 测试6:复杂查询") + fmt.Println("==================") + + ssql := streamsql.New() + defer ssql.Stop() + + // 复杂的聚合查询,结合自定义函数和嵌套字段 + sql := `SELECT + device.location as location, + AVG(square(sensor.temperature)) as avg_temp_squared, + MAX(f_to_c(sensor.temperature)) as max_celsius, + COUNT(*) as sample_count, + SUM(circle_area(device.radius)) as total_area + FROM stream + WHERE sensor.temperature > 20 AND device.status = 'online' + GROUP BY device.location, TumblingWindow('3s')` + + err := ssql.Execute(sql) + if err != nil { + fmt.Printf("❌ SQL执行失败: %v\n", err) + return + } + + ssql.AddSink(func(result interface{}) { + fmt.Printf(" 📊 复杂查询结果: %v\n", result) + }) + + // 发送复杂测试数据 + locations := []string{"room-A", "room-B", "room-C"} + for i := 0; i < 12; i++ { + location := locations[i%len(locations)] + data := map[string]interface{}{ + "device": map[string]interface{}{ + "location": location, + "status": "online", + "radius": 1.0 + rand.Float64()*2.0, // 1-3的随机半径 + }, + "sensor": map[string]interface{}{ + "temperature": 25.0 + rand.Float64()*10.0, // 25-35度 + "humidity": 50.0 + rand.Float64()*30.0, // 50-80% + }, + "timestamp": time.Now(), + } + ssql.Emit(data) + time.Sleep(300 * time.Millisecond) + } + + // 等待窗口触发 + time.Sleep(3 * time.Second) + ssql.Stream().Window.Trigger() + time.Sleep(500 * time.Millisecond) + fmt.Println(" ✅ 复杂查询测试完成") +} diff --git a/examples/custom-functions-demo/main.go b/examples/custom-functions-demo/main.go index c55f4b9..8ca9514 100644 --- a/examples/custom-functions-demo/main.go +++ b/examples/custom-functions-demo/main.go @@ -625,12 +625,12 @@ func testMathFunctions(ssql *streamsql.Streamsql) { } // 添加结果监听器 - ssql.Stream().AddSink(func(result interface{}) { + ssql.AddSink(func(result interface{}) { fmt.Printf(" 📊 数学函数结果: %v\n", result) }) for _, data := range testData { - ssql.AddData(data) + ssql.Emit(data) } time.Sleep(1 * time.Second) @@ -672,12 +672,12 @@ func testStringFunctions(ssql *streamsql.Streamsql) { }, } - ssql.Stream().AddSink(func(result interface{}) { + ssql.AddSink(func(result interface{}) { fmt.Printf(" 📊 字符串函数结果: %v\n", result) }) for _, data := range testData { - ssql.AddData(data) + ssql.Emit(data) } time.Sleep(500 * time.Millisecond) @@ -715,12 +715,12 @@ func testConversionFunctions(ssql *streamsql.Streamsql) { }, } - ssql.Stream().AddSink(func(result interface{}) { + ssql.AddSink(func(result interface{}) { fmt.Printf(" 📊 转换函数结果: %v\n", result) }) for _, data := range testData { - ssql.AddData(data) + ssql.Emit(data) } time.Sleep(500 * time.Millisecond) @@ -753,12 +753,12 @@ func testAggregateFunctions(ssql *streamsql.Streamsql) { map[string]interface{}{"device": "sensor1", "value": 128.0, "category": "A"}, } - ssql.Stream().AddSink(func(result interface{}) { + ssql.AddSink(func(result interface{}) { fmt.Printf(" 📊 聚合函数结果: %v\n", result) }) for _, data := range testData { - ssql.AddData(data) + ssql.Emit(data) } time.Sleep(1 * time.Second) diff --git a/examples/nested-field-examples/main.go b/examples/nested-field-examples/main.go index 34066f8..62beacd 100644 --- a/examples/nested-field-examples/main.go +++ b/examples/nested-field-examples/main.go @@ -1,625 +1,625 @@ -package main - -import ( - "context" - "fmt" - "math/rand" - "sync" - "time" - - "github.com/rulego/streamsql" -) - -func main() { - fmt.Println("🔧 StreamSQL 嵌套字段访问功能完整演示") - fmt.Println("=========================================") - - // 创建 StreamSQL 实例 - ssql := streamsql.New() - defer ssql.Stop() - - // 基础功能演示 - fmt.Println("\n📊 第一部分:基础嵌套字段访问") - demonstrateBasicNestedAccess(ssql) - - // 基础聚合演示 - fmt.Println("\n📈 第二部分:嵌套字段聚合") - demonstrateNestedAggregation(ssql) - - // 复杂功能演示 - fmt.Println("\n🔧 第三部分:复杂嵌套字段访问") - - // 演示1: 数组索引访问 - fmt.Println("\n📊 演示1: 数组索引访问") - demonstrateArrayAccess(ssql) - - // 演示2: Map键访问 - fmt.Println("\n🗝️ 演示2: Map键访问") - demonstrateMapKeyAccess(ssql) - - // 演示3: 混合复杂访问 - fmt.Println("\n🔄 演示3: 混合复杂访问") - demonstrateComplexMixedAccess(ssql) - - // 演示4: 负数索引访问 - fmt.Println("\n⬅️ 演示4: 负数索引访问") - demonstrateNegativeIndexAccess(ssql) - - // 演示5: 数组索引聚合计算 - fmt.Println("\n📈 演示5: 数组索引聚合计算") - demonstrateArrayIndexAggregation(ssql) - - fmt.Println("\n✅ 完整演示完成!") -} - -// 演示基础嵌套字段访问 -func demonstrateBasicNestedAccess(ssql *streamsql.Streamsql) { - // SQL查询使用基础嵌套字段 - rsql := `SELECT device.info.name as device_name, - device.location, - sensor.temperature, - sensor.humidity - FROM stream - WHERE device.location = 'room-A' - AND sensor.temperature > 20` - - err := ssql.Execute(rsql) - if err != nil { - fmt.Printf("❌ SQL执行失败: %v\n", err) - return - } - - // 准备测试数据 - testData := []map[string]interface{}{ - { - "device": map[string]interface{}{ - "info": map[string]interface{}{ - "name": "温度传感器-001", - "type": "temperature", - }, - "location": "room-A", - }, - "sensor": map[string]interface{}{ - "temperature": 25.5, - "humidity": 60.2, - }, - "timestamp": time.Now().Unix(), - }, - { - "device": map[string]interface{}{ - "info": map[string]interface{}{ - "name": "温度传感器-002", - "type": "temperature", - }, - "location": "room-B", // 不匹配条件 - }, - "sensor": map[string]interface{}{ - "temperature": 30.0, - "humidity": 55.8, - }, - "timestamp": time.Now().Unix(), - }, - { - "device": map[string]interface{}{ - "info": map[string]interface{}{ - "name": "温度传感器-003", - "type": "temperature", - }, - "location": "room-A", - }, - "sensor": map[string]interface{}{ - "temperature": 15.0, // 不匹配条件 - "humidity": 65.3, - }, - "timestamp": time.Now().Unix(), - }, - } - - var wg sync.WaitGroup - wg.Add(1) - - // 设置结果回调 - ssql.Stream().AddSink(func(result interface{}) { - defer wg.Done() - - fmt.Println(" 📋 基础嵌套字段访问结果:") - if resultSlice, ok := result.([]map[string]interface{}); ok { - for i, item := range resultSlice { - fmt.Printf(" 记录 %d:\n", i+1) - fmt.Printf(" 设备名称: %v\n", item["device_name"]) - fmt.Printf(" 设备位置: %v\n", item["device.location"]) - fmt.Printf(" 温度: %v°C\n", item["sensor.temperature"]) - fmt.Printf(" 湿度: %v%%\n", item["sensor.humidity"]) - fmt.Println() - } - } - }) - - // 添加测试数据 - for _, data := range testData { - ssql.Stream().AddData(data) - } - - // 等待结果 - wg.Wait() -} - -// 演示嵌套字段聚合 -func demonstrateNestedAggregation(ssql *streamsql.Streamsql) { - // SQL查询:嵌套字段聚合 - rsql := `SELECT device.location, - AVG(sensor.temperature) as avg_temp, - MAX(sensor.humidity) as max_humidity, - COUNT(*) as sensor_count - FROM stream - GROUP BY device.location, TumblingWindow('2s') - WITH (TIMESTAMP='timestamp', TIMEUNIT='ss')` - - err := ssql.Execute(rsql) - if err != nil { - fmt.Printf("❌ SQL执行失败: %v\n", err) - return - } - - var resultCount int - var wg sync.WaitGroup - wg.Add(1) - - // 设置结果回调 - ssql.Stream().AddSink(func(result interface{}) { - defer wg.Done() - - fmt.Println(" 📈 嵌套字段聚合结果:") - if resultSlice, ok := result.([]map[string]interface{}); ok { - for i, item := range resultSlice { - resultCount++ - fmt.Printf(" 聚合结果 %d:\n", i+1) - fmt.Printf(" 位置: %v\n", item["device.location"]) - fmt.Printf(" 平均温度: %.2f°C\n", item["avg_temp"]) - fmt.Printf(" 最大湿度: %.1f%%\n", item["max_humidity"]) - fmt.Printf(" 传感器数量: %v\n", item["sensor_count"]) - fmt.Println() - } - } - }) - - // 生成模拟数据 - locations := []string{"智能温室-A区", "智能温室-B区", "智能温室-C区"} - - go func() { - for i := 0; i < 9; i++ { - location := locations[rand.Intn(len(locations))] - - data := map[string]interface{}{ - "device": map[string]interface{}{ - "info": map[string]interface{}{ - "name": fmt.Sprintf("sensor-%03d", i+1), - "type": "environment", - }, - "location": location, - }, - "sensor": map[string]interface{}{ - "temperature": 18.0 + rand.Float64()*15.0, // 18-33°C - "humidity": 40.0 + rand.Float64()*30.0, // 40-70% - }, - "timestamp": time.Now().Unix(), - } - - ssql.Stream().AddData(data) - time.Sleep(300 * time.Millisecond) // 每300ms发送一条数据 - } - }() - - // 等待聚合结果 - ctx, cancel := context.WithTimeout(context.Background(), 6*time.Second) - defer cancel() - - select { - case <-ctx.Done(): - fmt.Println(" ⏰ 聚合计算超时") - case <-func() chan struct{} { - done := make(chan struct{}) - go func() { - wg.Wait() - close(done) - }() - return done - }(): - fmt.Printf(" ✅ 聚合计算完成,共生成 %d 个窗口结果\n", resultCount) - } -} - -// 演示数组索引访问 -func demonstrateArrayAccess(ssql *streamsql.Streamsql) { - // SQL查询:提取数组中的特定元素 - rsql := `SELECT device, - sensors[0].temperature as first_sensor_temp, - sensors[1].humidity as second_sensor_humidity, - data[2] as third_data_item - FROM stream` - - err := ssql.Execute(rsql) - if err != nil { - fmt.Printf("❌ SQL执行失败: %v\n", err) - return - } - - // 准备测试数据 - testData := []map[string]interface{}{ - { - "device": "工业传感器-001", - "sensors": []interface{}{ - map[string]interface{}{"temperature": 25.5, "humidity": 60.2}, - map[string]interface{}{"temperature": 26.8, "humidity": 58.7}, - map[string]interface{}{"temperature": 24.1, "humidity": 62.1}, - }, - "data": []interface{}{"status_ok", "battery_95%", "signal_strong", "location_A1"}, - "timestamp": time.Now().Unix(), - }, - { - "device": "环境监测器-002", - "sensors": []interface{}{ - map[string]interface{}{"temperature": 22.3, "humidity": 65.8}, - map[string]interface{}{"temperature": 23.1, "humidity": 63.2}, - }, - "data": []interface{}{"status_warning", "battery_78%", "signal_weak"}, - "timestamp": time.Now().Unix(), - }, - } - - var wg sync.WaitGroup - wg.Add(1) - - // 设置结果回调 - ssql.Stream().AddSink(func(result interface{}) { - defer wg.Done() - - fmt.Println(" 📋 数组索引访问结果:") - if resultSlice, ok := result.([]map[string]interface{}); ok { - for i, item := range resultSlice { - fmt.Printf(" 记录 %d:\n", i+1) - fmt.Printf(" 设备: %v\n", item["device"]) - fmt.Printf(" 第一个传感器温度: %v°C\n", item["first_sensor_temp"]) - fmt.Printf(" 第二个传感器湿度: %v%%\n", item["second_sensor_humidity"]) - fmt.Printf(" 第三个数据项: %v\n", item["third_data_item"]) - fmt.Println() - } - } - }) - - // 添加测试数据 - for _, data := range testData { - ssql.Stream().AddData(data) - } - - // 等待结果 - wg.Wait() -} - -// 演示Map键访问 -func demonstrateMapKeyAccess(ssql *streamsql.Streamsql) { - // SQL查询:使用字符串键访问Map数据 - rsql := `SELECT device_id, - config['host'] as server_host, - config["port"] as server_port, - settings['enable_ssl'] as ssl_enabled, - metadata["version"] as app_version - FROM stream` - - err := ssql.Execute(rsql) - if err != nil { - fmt.Printf("❌ SQL执行失败: %v\n", err) - return - } - - // 准备测试数据 - testData := []map[string]interface{}{ - { - "device_id": "gateway-001", - "config": map[string]interface{}{ - "host": "192.168.1.100", - "port": 8080, - "protocol": "https", - }, - "settings": map[string]interface{}{ - "enable_ssl": true, - "timeout": 30, - "max_retries": 3, - }, - "metadata": map[string]interface{}{ - "version": "v2.1.3", - "build_date": "2023-12-01", - "vendor": "TechCorp", - }, - }, - { - "device_id": "gateway-002", - "config": map[string]interface{}{ - "host": "192.168.1.101", - "port": 8443, - "protocol": "https", - }, - "settings": map[string]interface{}{ - "enable_ssl": false, - "timeout": 60, - "max_retries": 5, - }, - "metadata": map[string]interface{}{ - "version": "v2.0.8", - "build_date": "2023-11-15", - "vendor": "TechCorp", - }, - }, - } - - var wg sync.WaitGroup - wg.Add(1) - - // 设置结果回调 - ssql.Stream().AddSink(func(result interface{}) { - defer wg.Done() - - fmt.Println(" 🗝️ Map键访问结果:") - if resultSlice, ok := result.([]map[string]interface{}); ok { - for i, item := range resultSlice { - fmt.Printf(" 记录 %d:\n", i+1) - fmt.Printf(" 设备ID: %v\n", item["device_id"]) - fmt.Printf(" 服务器主机: %v\n", item["server_host"]) - fmt.Printf(" 服务器端口: %v\n", item["server_port"]) - fmt.Printf(" SSL启用: %v\n", item["ssl_enabled"]) - fmt.Printf(" 应用版本: %v\n", item["app_version"]) - fmt.Println() - } - } - }) - - // 添加测试数据 - for _, data := range testData { - ssql.Stream().AddData(data) - } - - // 等待结果 - wg.Wait() -} - -// 演示混合复杂访问 -func demonstrateComplexMixedAccess(ssql *streamsql.Streamsql) { - // SQL查询:混合使用数组索引、Map键和嵌套字段访问 - rsql := `SELECT building, - floors[0].rooms[2]['name'] as first_floor_room3_name, - floors[1].sensors[0].readings['temperature'] as second_floor_first_sensor_temp, - metadata.building_info['architect'] as building_architect, - alerts[-1].message as latest_alert - FROM stream` - - err := ssql.Execute(rsql) - if err != nil { - fmt.Printf("❌ SQL执行失败: %v\n", err) - return - } - - // 准备复杂嵌套数据 - testData := map[string]interface{}{ - "building": "智能大厦A座", - "floors": []interface{}{ - // 第一层 - map[string]interface{}{ - "floor_number": 1, - "rooms": []interface{}{ - map[string]interface{}{"name": "大厅", "type": "public"}, - map[string]interface{}{"name": "接待室", "type": "office"}, - map[string]interface{}{"name": "会议室A", "type": "meeting"}, - map[string]interface{}{"name": "休息区", "type": "lounge"}, - }, - }, - // 第二层 - map[string]interface{}{ - "floor_number": 2, - "sensors": []interface{}{ - map[string]interface{}{ - "id": "sensor-201", - "readings": map[string]interface{}{ - "temperature": 23.5, - "humidity": 58.2, - "co2": 420, - }, - }, - map[string]interface{}{ - "id": "sensor-202", - "readings": map[string]interface{}{ - "temperature": 24.1, - "humidity": 60.8, - "co2": 380, - }, - }, - }, - }, - }, - "metadata": map[string]interface{}{ - "building_info": map[string]interface{}{ - "architect": "张建筑师", - "year_built": 2020, - "total_floors": 25, - }, - "owner": "科技园管委会", - }, - "alerts": []interface{}{ - map[string]interface{}{"level": "info", "message": "系统启动完成"}, - map[string]interface{}{"level": "warning", "message": "传感器信号弱"}, - map[string]interface{}{"level": "info", "message": "定期维护提醒"}, - }, - } - - var wg sync.WaitGroup - wg.Add(1) - - // 设置结果回调 - ssql.Stream().AddSink(func(result interface{}) { - defer wg.Done() - - fmt.Println(" 🔄 混合复杂访问结果:") - if resultSlice, ok := result.([]map[string]interface{}); ok { - for i, item := range resultSlice { - fmt.Printf(" 记录 %d:\n", i+1) - fmt.Printf(" 建筑: %v\n", item["building"]) - fmt.Printf(" 一层第3个房间: %v\n", item["first_floor_room3_name"]) - fmt.Printf(" 二层第1个传感器温度: %v°C\n", item["second_floor_first_sensor_temp"]) - fmt.Printf(" 建筑师: %v\n", item["building_architect"]) - fmt.Printf(" 最新警报: %v\n", item["latest_alert"]) - fmt.Println() - } - } - }) - - // 添加数据 - ssql.Stream().AddData(testData) - - // 等待结果 - wg.Wait() -} - -// 演示负数索引访问 -func demonstrateNegativeIndexAccess(ssql *streamsql.Streamsql) { - // SQL查询:使用负数索引访问数组末尾元素 - rsql := `SELECT device_name, - readings[-1] as latest_reading, - history[-2] as second_last_event, - tags[-1] as last_tag - FROM stream` - - err := ssql.Execute(rsql) - if err != nil { - fmt.Printf("❌ SQL执行失败: %v\n", err) - return - } - - // 准备测试数据 - testData := []map[string]interface{}{ - { - "device_name": "温度监测器-Alpha", - "readings": []interface{}{18.5, 19.2, 20.1, 21.3, 22.8, 23.5}, // [-1] = 23.5 - "history": []interface{}{"boot", "calibration", "running", "alert", "resolved"}, // [-2] = "alert" - "tags": []interface{}{"indoor", "critical", "monitored"}, // [-1] = "monitored" - }, - { - "device_name": "湿度传感器-Beta", - "readings": []interface{}{45.2, 47.8, 52.1, 48.9}, // [-1] = 48.9 - "history": []interface{}{"init", "testing", "deployed"}, // [-2] = "testing" - "tags": []interface{}{"outdoor", "backup"}, // [-1] = "backup" - }, - } - - var wg sync.WaitGroup - wg.Add(1) - - // 设置结果回调 - ssql.Stream().AddSink(func(result interface{}) { - defer wg.Done() - - fmt.Println(" ⬅️ 负数索引访问结果:") - if resultSlice, ok := result.([]map[string]interface{}); ok { - for i, item := range resultSlice { - fmt.Printf(" 记录 %d:\n", i+1) - fmt.Printf(" 设备名称: %v\n", item["device_name"]) - fmt.Printf(" 最新读数: %v\n", item["latest_reading"]) - fmt.Printf(" 倒数第二个事件: %v\n", item["second_last_event"]) - fmt.Printf(" 最后一个标签: %v\n", item["last_tag"]) - fmt.Println() - } - } - }) - - // 添加测试数据 - for _, data := range testData { - ssql.Stream().AddData(data) - } - - // 等待结果 - wg.Wait() -} - -// 演示数组索引聚合计算 -func demonstrateArrayIndexAggregation(ssql *streamsql.Streamsql) { - // SQL查询:对数组中特定位置的数据进行聚合计算 - rsql := `SELECT location, - AVG(sensors[0].temperature) as avg_first_sensor_temp, - MAX(sensors[1].humidity) as max_second_sensor_humidity, - COUNT(*) as device_count - FROM stream - GROUP BY location, TumblingWindow('2s') - WITH (TIMESTAMP='timestamp', TIMEUNIT='ss')` - - err := ssql.Execute(rsql) - if err != nil { - fmt.Printf("❌ SQL执行失败: %v\n", err) - return - } - - var resultCount int - var wg sync.WaitGroup - wg.Add(1) - - // 设置结果回调 - ssql.Stream().AddSink(func(result interface{}) { - defer wg.Done() - - fmt.Println(" 📈 数组索引聚合计算结果:") - if resultSlice, ok := result.([]map[string]interface{}); ok { - for i, item := range resultSlice { - resultCount++ - fmt.Printf(" 聚合结果 %d:\n", i+1) - fmt.Printf(" 位置: %v\n", item["location"]) - fmt.Printf(" 第一个传感器平均温度: %.2f°C\n", item["avg_first_sensor_temp"]) - fmt.Printf(" 第二个传感器最大湿度: %.1f%%\n", item["max_second_sensor_humidity"]) - fmt.Printf(" 设备数量: %v\n", item["device_count"]) - fmt.Println() - } - } - }) - - // 生成模拟数据 - locations := []string{"车间A", "车间B", "车间C"} - - go func() { - for i := 0; i < 12; i++ { - location := locations[rand.Intn(len(locations))] - - data := map[string]interface{}{ - "device_id": fmt.Sprintf("device-%03d", i+1), - "location": location, - "sensors": []interface{}{ - map[string]interface{}{ - "temperature": 20.0 + rand.Float64()*10.0, // 20-30°C - "humidity": 50.0 + rand.Float64()*20.0, // 50-70% - }, - map[string]interface{}{ - "temperature": 18.0 + rand.Float64()*12.0, // 18-30°C - "humidity": 45.0 + rand.Float64()*25.0, // 45-70% - }, - }, - "timestamp": time.Now().Unix(), - } - - ssql.Stream().AddData(data) - time.Sleep(200 * time.Millisecond) // 每200ms发送一条数据 - } - }() - - // 等待聚合结果 - ctx, cancel := context.WithTimeout(context.Background(), 8*time.Second) - defer cancel() - - select { - case <-ctx.Done(): - fmt.Println(" ⏰ 聚合计算超时") - case <-func() chan struct{} { - done := make(chan struct{}) - go func() { - wg.Wait() - close(done) - }() - return done - }(): - fmt.Printf(" ✅ 聚合计算完成,共生成 %d 个窗口结果\n", resultCount) - } -} +package main + +import ( + "context" + "fmt" + "math/rand" + "sync" + "time" + + "github.com/rulego/streamsql" +) + +func main() { + fmt.Println("🔧 StreamSQL 嵌套字段访问功能完整演示") + fmt.Println("=========================================") + + // 创建 StreamSQL 实例 + ssql := streamsql.New() + defer ssql.Stop() + + // 基础功能演示 + fmt.Println("\n📊 第一部分:基础嵌套字段访问") + demonstrateBasicNestedAccess(ssql) + + // 基础聚合演示 + fmt.Println("\n📈 第二部分:嵌套字段聚合") + demonstrateNestedAggregation(ssql) + + // 复杂功能演示 + fmt.Println("\n🔧 第三部分:复杂嵌套字段访问") + + // 演示1: 数组索引访问 + fmt.Println("\n📊 演示1: 数组索引访问") + demonstrateArrayAccess(ssql) + + // 演示2: Map键访问 + fmt.Println("\n🗝️ 演示2: Map键访问") + demonstrateMapKeyAccess(ssql) + + // 演示3: 混合复杂访问 + fmt.Println("\n🔄 演示3: 混合复杂访问") + demonstrateComplexMixedAccess(ssql) + + // 演示4: 负数索引访问 + fmt.Println("\n⬅️ 演示4: 负数索引访问") + demonstrateNegativeIndexAccess(ssql) + + // 演示5: 数组索引聚合计算 + fmt.Println("\n📈 演示5: 数组索引聚合计算") + demonstrateArrayIndexAggregation(ssql) + + fmt.Println("\n✅ 完整演示完成!") +} + +// 演示基础嵌套字段访问 +func demonstrateBasicNestedAccess(ssql *streamsql.Streamsql) { + // SQL查询使用基础嵌套字段 + rsql := `SELECT device.info.name as device_name, + device.location, + sensor.temperature, + sensor.humidity + FROM stream + WHERE device.location = 'room-A' + AND sensor.temperature > 20` + + err := ssql.Execute(rsql) + if err != nil { + fmt.Printf("❌ SQL执行失败: %v\n", err) + return + } + + // 准备测试数据 + testData := []map[string]interface{}{ + { + "device": map[string]interface{}{ + "info": map[string]interface{}{ + "name": "温度传感器-001", + "type": "temperature", + }, + "location": "room-A", + }, + "sensor": map[string]interface{}{ + "temperature": 25.5, + "humidity": 60.2, + }, + "timestamp": time.Now().Unix(), + }, + { + "device": map[string]interface{}{ + "info": map[string]interface{}{ + "name": "温度传感器-002", + "type": "temperature", + }, + "location": "room-B", // 不匹配条件 + }, + "sensor": map[string]interface{}{ + "temperature": 30.0, + "humidity": 55.8, + }, + "timestamp": time.Now().Unix(), + }, + { + "device": map[string]interface{}{ + "info": map[string]interface{}{ + "name": "温度传感器-003", + "type": "temperature", + }, + "location": "room-A", + }, + "sensor": map[string]interface{}{ + "temperature": 15.0, // 不匹配条件 + "humidity": 65.3, + }, + "timestamp": time.Now().Unix(), + }, + } + + var wg sync.WaitGroup + wg.Add(1) + + // 设置结果回调 + ssql.AddSink(func(result interface{}) { + defer wg.Done() + + fmt.Println(" 📋 基础嵌套字段访问结果:") + if resultSlice, ok := result.([]map[string]interface{}); ok { + for i, item := range resultSlice { + fmt.Printf(" 记录 %d:\n", i+1) + fmt.Printf(" 设备名称: %v\n", item["device_name"]) + fmt.Printf(" 设备位置: %v\n", item["device.location"]) + fmt.Printf(" 温度: %v°C\n", item["sensor.temperature"]) + fmt.Printf(" 湿度: %v%%\n", item["sensor.humidity"]) + fmt.Println() + } + } + }) + + // 添加测试数据 + for _, data := range testData { + ssql.Emit(data) + } + + // 等待结果 + wg.Wait() +} + +// 演示嵌套字段聚合 +func demonstrateNestedAggregation(ssql *streamsql.Streamsql) { + // SQL查询:嵌套字段聚合 + rsql := `SELECT device.location, + AVG(sensor.temperature) as avg_temp, + MAX(sensor.humidity) as max_humidity, + COUNT(*) as sensor_count + FROM stream + GROUP BY device.location, TumblingWindow('2s') + WITH (TIMESTAMP='timestamp', TIMEUNIT='ss')` + + err := ssql.Execute(rsql) + if err != nil { + fmt.Printf("❌ SQL执行失败: %v\n", err) + return + } + + var resultCount int + var wg sync.WaitGroup + wg.Add(1) + + // 设置结果回调 + ssql.AddSink(func(result interface{}) { + defer wg.Done() + + fmt.Println(" 📈 嵌套字段聚合结果:") + if resultSlice, ok := result.([]map[string]interface{}); ok { + for i, item := range resultSlice { + resultCount++ + fmt.Printf(" 聚合结果 %d:\n", i+1) + fmt.Printf(" 位置: %v\n", item["device.location"]) + fmt.Printf(" 平均温度: %.2f°C\n", item["avg_temp"]) + fmt.Printf(" 最大湿度: %.1f%%\n", item["max_humidity"]) + fmt.Printf(" 传感器数量: %v\n", item["sensor_count"]) + fmt.Println() + } + } + }) + + // 生成模拟数据 + locations := []string{"智能温室-A区", "智能温室-B区", "智能温室-C区"} + + go func() { + for i := 0; i < 9; i++ { + location := locations[rand.Intn(len(locations))] + + data := map[string]interface{}{ + "device": map[string]interface{}{ + "info": map[string]interface{}{ + "name": fmt.Sprintf("sensor-%03d", i+1), + "type": "environment", + }, + "location": location, + }, + "sensor": map[string]interface{}{ + "temperature": 18.0 + rand.Float64()*15.0, // 18-33°C + "humidity": 40.0 + rand.Float64()*30.0, // 40-70% + }, + "timestamp": time.Now().Unix(), + } + + ssql.Emit(data) + time.Sleep(300 * time.Millisecond) // 每300ms发送一条数据 + } + }() + + // 等待聚合结果 + ctx, cancel := context.WithTimeout(context.Background(), 6*time.Second) + defer cancel() + + select { + case <-ctx.Done(): + fmt.Println(" ⏰ 聚合计算超时") + case <-func() chan struct{} { + done := make(chan struct{}) + go func() { + wg.Wait() + close(done) + }() + return done + }(): + fmt.Printf(" ✅ 聚合计算完成,共生成 %d 个窗口结果\n", resultCount) + } +} + +// 演示数组索引访问 +func demonstrateArrayAccess(ssql *streamsql.Streamsql) { + // SQL查询:提取数组中的特定元素 + rsql := `SELECT device, + sensors[0].temperature as first_sensor_temp, + sensors[1].humidity as second_sensor_humidity, + data[2] as third_data_item + FROM stream` + + err := ssql.Execute(rsql) + if err != nil { + fmt.Printf("❌ SQL执行失败: %v\n", err) + return + } + + // 准备测试数据 + testData := []map[string]interface{}{ + { + "device": "工业传感器-001", + "sensors": []interface{}{ + map[string]interface{}{"temperature": 25.5, "humidity": 60.2}, + map[string]interface{}{"temperature": 26.8, "humidity": 58.7}, + map[string]interface{}{"temperature": 24.1, "humidity": 62.1}, + }, + "data": []interface{}{"status_ok", "battery_95%", "signal_strong", "location_A1"}, + "timestamp": time.Now().Unix(), + }, + { + "device": "环境监测器-002", + "sensors": []interface{}{ + map[string]interface{}{"temperature": 22.3, "humidity": 65.8}, + map[string]interface{}{"temperature": 23.1, "humidity": 63.2}, + }, + "data": []interface{}{"status_warning", "battery_78%", "signal_weak"}, + "timestamp": time.Now().Unix(), + }, + } + + var wg sync.WaitGroup + wg.Add(1) + + // 设置结果回调 + ssql.AddSink(func(result interface{}) { + defer wg.Done() + + fmt.Println(" 📋 数组索引访问结果:") + if resultSlice, ok := result.([]map[string]interface{}); ok { + for i, item := range resultSlice { + fmt.Printf(" 记录 %d:\n", i+1) + fmt.Printf(" 设备: %v\n", item["device"]) + fmt.Printf(" 第一个传感器温度: %v°C\n", item["first_sensor_temp"]) + fmt.Printf(" 第二个传感器湿度: %v%%\n", item["second_sensor_humidity"]) + fmt.Printf(" 第三个数据项: %v\n", item["third_data_item"]) + fmt.Println() + } + } + }) + + // 添加测试数据 + for _, data := range testData { + ssql.Emit(data) + } + + // 等待结果 + wg.Wait() +} + +// 演示Map键访问 +func demonstrateMapKeyAccess(ssql *streamsql.Streamsql) { + // SQL查询:使用字符串键访问Map数据 + rsql := `SELECT device_id, + config['host'] as server_host, + config["port"] as server_port, + settings['enable_ssl'] as ssl_enabled, + metadata["version"] as app_version + FROM stream` + + err := ssql.Execute(rsql) + if err != nil { + fmt.Printf("❌ SQL执行失败: %v\n", err) + return + } + + // 准备测试数据 + testData := []map[string]interface{}{ + { + "device_id": "gateway-001", + "config": map[string]interface{}{ + "host": "192.168.1.100", + "port": 8080, + "protocol": "https", + }, + "settings": map[string]interface{}{ + "enable_ssl": true, + "timeout": 30, + "max_retries": 3, + }, + "metadata": map[string]interface{}{ + "version": "v2.1.3", + "build_date": "2023-12-01", + "vendor": "TechCorp", + }, + }, + { + "device_id": "gateway-002", + "config": map[string]interface{}{ + "host": "192.168.1.101", + "port": 8443, + "protocol": "https", + }, + "settings": map[string]interface{}{ + "enable_ssl": false, + "timeout": 60, + "max_retries": 5, + }, + "metadata": map[string]interface{}{ + "version": "v2.0.8", + "build_date": "2023-11-15", + "vendor": "TechCorp", + }, + }, + } + + var wg sync.WaitGroup + wg.Add(1) + + // 设置结果回调 + ssql.AddSink(func(result interface{}) { + defer wg.Done() + + fmt.Println(" 🗝️ Map键访问结果:") + if resultSlice, ok := result.([]map[string]interface{}); ok { + for i, item := range resultSlice { + fmt.Printf(" 记录 %d:\n", i+1) + fmt.Printf(" 设备ID: %v\n", item["device_id"]) + fmt.Printf(" 服务器主机: %v\n", item["server_host"]) + fmt.Printf(" 服务器端口: %v\n", item["server_port"]) + fmt.Printf(" SSL启用: %v\n", item["ssl_enabled"]) + fmt.Printf(" 应用版本: %v\n", item["app_version"]) + fmt.Println() + } + } + }) + + // 添加测试数据 + for _, data := range testData { + ssql.Emit(data) + } + + // 等待结果 + wg.Wait() +} + +// 演示混合复杂访问 +func demonstrateComplexMixedAccess(ssql *streamsql.Streamsql) { + // SQL查询:混合使用数组索引、Map键和嵌套字段访问 + rsql := `SELECT building, + floors[0].rooms[2]['name'] as first_floor_room3_name, + floors[1].sensors[0].readings['temperature'] as second_floor_first_sensor_temp, + metadata.building_info['architect'] as building_architect, + alerts[-1].message as latest_alert + FROM stream` + + err := ssql.Execute(rsql) + if err != nil { + fmt.Printf("❌ SQL执行失败: %v\n", err) + return + } + + // 准备复杂嵌套数据 + testData := map[string]interface{}{ + "building": "智能大厦A座", + "floors": []interface{}{ + // 第一层 + map[string]interface{}{ + "floor_number": 1, + "rooms": []interface{}{ + map[string]interface{}{"name": "大厅", "type": "public"}, + map[string]interface{}{"name": "接待室", "type": "office"}, + map[string]interface{}{"name": "会议室A", "type": "meeting"}, + map[string]interface{}{"name": "休息区", "type": "lounge"}, + }, + }, + // 第二层 + map[string]interface{}{ + "floor_number": 2, + "sensors": []interface{}{ + map[string]interface{}{ + "id": "sensor-201", + "readings": map[string]interface{}{ + "temperature": 23.5, + "humidity": 58.2, + "co2": 420, + }, + }, + map[string]interface{}{ + "id": "sensor-202", + "readings": map[string]interface{}{ + "temperature": 24.1, + "humidity": 60.8, + "co2": 380, + }, + }, + }, + }, + }, + "metadata": map[string]interface{}{ + "building_info": map[string]interface{}{ + "architect": "张建筑师", + "year_built": 2020, + "total_floors": 25, + }, + "owner": "科技园管委会", + }, + "alerts": []interface{}{ + map[string]interface{}{"level": "info", "message": "系统启动完成"}, + map[string]interface{}{"level": "warning", "message": "传感器信号弱"}, + map[string]interface{}{"level": "info", "message": "定期维护提醒"}, + }, + } + + var wg sync.WaitGroup + wg.Add(1) + + // 设置结果回调 + ssql.AddSink(func(result interface{}) { + defer wg.Done() + + fmt.Println(" 🔄 混合复杂访问结果:") + if resultSlice, ok := result.([]map[string]interface{}); ok { + for i, item := range resultSlice { + fmt.Printf(" 记录 %d:\n", i+1) + fmt.Printf(" 建筑: %v\n", item["building"]) + fmt.Printf(" 一层第3个房间: %v\n", item["first_floor_room3_name"]) + fmt.Printf(" 二层第1个传感器温度: %v°C\n", item["second_floor_first_sensor_temp"]) + fmt.Printf(" 建筑师: %v\n", item["building_architect"]) + fmt.Printf(" 最新警报: %v\n", item["latest_alert"]) + fmt.Println() + } + } + }) + + // 添加数据 + ssql.Emit(testData) + + // 等待结果 + wg.Wait() +} + +// 演示负数索引访问 +func demonstrateNegativeIndexAccess(ssql *streamsql.Streamsql) { + // SQL查询:使用负数索引访问数组末尾元素 + rsql := `SELECT device_name, + readings[-1] as latest_reading, + history[-2] as second_last_event, + tags[-1] as last_tag + FROM stream` + + err := ssql.Execute(rsql) + if err != nil { + fmt.Printf("❌ SQL执行失败: %v\n", err) + return + } + + // 准备测试数据 + testData := []map[string]interface{}{ + { + "device_name": "温度监测器-Alpha", + "readings": []interface{}{18.5, 19.2, 20.1, 21.3, 22.8, 23.5}, // [-1] = 23.5 + "history": []interface{}{"boot", "calibration", "running", "alert", "resolved"}, // [-2] = "alert" + "tags": []interface{}{"indoor", "critical", "monitored"}, // [-1] = "monitored" + }, + { + "device_name": "湿度传感器-Beta", + "readings": []interface{}{45.2, 47.8, 52.1, 48.9}, // [-1] = 48.9 + "history": []interface{}{"init", "testing", "deployed"}, // [-2] = "testing" + "tags": []interface{}{"outdoor", "backup"}, // [-1] = "backup" + }, + } + + var wg sync.WaitGroup + wg.Add(1) + + // 设置结果回调 + ssql.AddSink(func(result interface{}) { + defer wg.Done() + + fmt.Println(" ⬅️ 负数索引访问结果:") + if resultSlice, ok := result.([]map[string]interface{}); ok { + for i, item := range resultSlice { + fmt.Printf(" 记录 %d:\n", i+1) + fmt.Printf(" 设备名称: %v\n", item["device_name"]) + fmt.Printf(" 最新读数: %v\n", item["latest_reading"]) + fmt.Printf(" 倒数第二个事件: %v\n", item["second_last_event"]) + fmt.Printf(" 最后一个标签: %v\n", item["last_tag"]) + fmt.Println() + } + } + }) + + // 添加测试数据 + for _, data := range testData { + ssql.Emit(data) + } + + // 等待结果 + wg.Wait() +} + +// 演示数组索引聚合计算 +func demonstrateArrayIndexAggregation(ssql *streamsql.Streamsql) { + // SQL查询:对数组中特定位置的数据进行聚合计算 + rsql := `SELECT location, + AVG(sensors[0].temperature) as avg_first_sensor_temp, + MAX(sensors[1].humidity) as max_second_sensor_humidity, + COUNT(*) as device_count + FROM stream + GROUP BY location, TumblingWindow('2s') + WITH (TIMESTAMP='timestamp', TIMEUNIT='ss')` + + err := ssql.Execute(rsql) + if err != nil { + fmt.Printf("❌ SQL执行失败: %v\n", err) + return + } + + var resultCount int + var wg sync.WaitGroup + wg.Add(1) + + // 设置结果回调 + ssql.AddSink(func(result interface{}) { + defer wg.Done() + + fmt.Println(" 📈 数组索引聚合计算结果:") + if resultSlice, ok := result.([]map[string]interface{}); ok { + for i, item := range resultSlice { + resultCount++ + fmt.Printf(" 聚合结果 %d:\n", i+1) + fmt.Printf(" 位置: %v\n", item["location"]) + fmt.Printf(" 第一个传感器平均温度: %.2f°C\n", item["avg_first_sensor_temp"]) + fmt.Printf(" 第二个传感器最大湿度: %.1f%%\n", item["max_second_sensor_humidity"]) + fmt.Printf(" 设备数量: %v\n", item["device_count"]) + fmt.Println() + } + } + }) + + // 生成模拟数据 + locations := []string{"车间A", "车间B", "车间C"} + + go func() { + for i := 0; i < 12; i++ { + location := locations[rand.Intn(len(locations))] + + data := map[string]interface{}{ + "device_id": fmt.Sprintf("device-%03d", i+1), + "location": location, + "sensors": []interface{}{ + map[string]interface{}{ + "temperature": 20.0 + rand.Float64()*10.0, // 20-30°C + "humidity": 50.0 + rand.Float64()*20.0, // 50-70% + }, + map[string]interface{}{ + "temperature": 18.0 + rand.Float64()*12.0, // 18-30°C + "humidity": 45.0 + rand.Float64()*25.0, // 45-70% + }, + }, + "timestamp": time.Now().Unix(), + } + + ssql.Emit(data) + time.Sleep(200 * time.Millisecond) // 每200ms发送一条数据 + } + }() + + // 等待聚合结果 + ctx, cancel := context.WithTimeout(context.Background(), 8*time.Second) + defer cancel() + + select { + case <-ctx.Done(): + fmt.Println(" ⏰ 聚合计算超时") + case <-func() chan struct{} { + done := make(chan struct{}) + go func() { + wg.Wait() + close(done) + }() + return done + }(): + fmt.Printf(" ✅ 聚合计算完成,共生成 %d 个窗口结果\n", resultCount) + } +} diff --git a/examples/non-aggregation/main.go b/examples/non-aggregation/main.go index bb12259..2162009 100644 --- a/examples/non-aggregation/main.go +++ b/examples/non-aggregation/main.go @@ -1,343 +1,343 @@ -package main - -import ( - "fmt" - "math/rand" - "time" - - "github.com/rulego/streamsql" -) - -// 非聚合场景使用示例 -// 展示StreamSQL在实时数据转换、过滤、清洗等场景中的应用 -func main() { - fmt.Println("=== StreamSQL 非聚合场景演示 ===") - - // 场景1: 实时数据清洗和标准化 - fmt.Println("\n1. 实时数据清洗和标准化") - demonstrateDataCleaning() - - // 场景2: 数据富化和计算字段 - fmt.Println("\n2. 数据富化和计算字段") - demonstrateDataEnrichment() - - // 场景3: 实时告警和事件过滤 - fmt.Println("\n3. 实时告警和事件过滤") - demonstrateRealTimeAlerting() - - // 场景4: 数据格式转换 - fmt.Println("\n4. 数据格式转换") - demonstrateDataFormatConversion() - - // 场景5: 基于条件的数据路由 - fmt.Println("\n5. 基于条件的数据路由") - demonstrateDataRouting() - - // 场景6: 嵌套字段处理 - fmt.Println("\n6. 嵌套字段处理") - demonstrateNestedFieldProcessing() - - fmt.Println("\n=== 演示完成 ===") -} - -// 场景1: 实时数据清洗和标准化 -func demonstrateDataCleaning() { - ssql := streamsql.New() - defer ssql.Stop() - - // 清洗和标准化SQL - rsql := `SELECT deviceId, - UPPER(TRIM(deviceType)) as device_type, - ROUND(temperature, 2) as temperature, - COALESCE(location, 'unknown') as location, - CASE WHEN status = 1 THEN 'active' - WHEN status = 0 THEN 'inactive' - ELSE 'unknown' END as status_text - FROM stream - WHERE deviceId != '' AND temperature > -999` - - err := ssql.Execute(rsql) - if err != nil { - panic(err) - } - - // 结果处理 - ssql.Stream().AddSink(func(result interface{}) { - fmt.Printf(" 清洗后数据: %+v\n", result) - }) - - // 模拟脏数据输入 - dirtyData := []map[string]interface{}{ - {"deviceId": "sensor001", "deviceType": " temperature ", "temperature": 25.456789, "location": "room1", "status": 1}, - {"deviceId": "sensor002", "deviceType": "humidity", "temperature": 60.123, "location": nil, "status": 0}, - {"deviceId": "", "deviceType": "pressure", "temperature": nil, "location": "room2", "status": 2}, // 应被过滤 - {"deviceId": "sensor003", "deviceType": "TEMPERATURE", "temperature": 22.7, "location": "room3", "status": 1}, - } - - for _, data := range dirtyData { - ssql.Stream().AddData(data) - time.Sleep(50 * time.Millisecond) - } - - time.Sleep(200 * time.Millisecond) -} - -// 场景2: 数据富化和计算字段 -func demonstrateDataEnrichment() { - ssql := streamsql.New() - defer ssql.Stop() - - // 数据富化SQL - rsql := `SELECT *, - temperature * 1.8 + 32 as temp_fahrenheit, - CASE WHEN temperature > 30 THEN 'hot' - WHEN temperature < 15 THEN 'cold' - ELSE 'normal' END as temp_category, - CONCAT(location, '-', deviceId) as full_identifier, - NOW() as processed_timestamp, - ROUND(humidity / 100.0, 4) as humidity_ratio - FROM stream` - - err := ssql.Execute(rsql) - if err != nil { - panic(err) - } - - ssql.Stream().AddSink(func(result interface{}) { - fmt.Printf(" 富化后数据: %+v\n", result) - }) - - // 原始数据 - rawData := []map[string]interface{}{ - {"deviceId": "sensor001", "temperature": 32.5, "humidity": 65, "location": "greenhouse"}, - {"deviceId": "sensor002", "temperature": 12.0, "humidity": 45, "location": "warehouse"}, - {"deviceId": "sensor003", "temperature": 22.8, "humidity": 70, "location": "office"}, - } - - for _, data := range rawData { - ssql.Stream().AddData(data) - time.Sleep(100 * time.Millisecond) - } - - time.Sleep(200 * time.Millisecond) -} - -// 场景3: 实时告警和事件过滤 -func demonstrateRealTimeAlerting() { - ssql := streamsql.New() - defer ssql.Stop() - - // 告警过滤SQL - rsql := `SELECT deviceId, - temperature, - humidity, - location, - 'CRITICAL' as alert_level, - CASE WHEN temperature > 40 THEN 'High Temperature Alert' - WHEN temperature < 5 THEN 'Low Temperature Alert' - WHEN humidity > 90 THEN 'High Humidity Alert' - WHEN humidity < 20 THEN 'Low Humidity Alert' - ELSE 'Unknown Alert' END as alert_message, - NOW() as alert_time - FROM stream - WHERE temperature > 40 OR temperature < 5 OR humidity > 90 OR humidity < 20` - - err := ssql.Execute(rsql) - if err != nil { - panic(err) - } - - ssql.Stream().AddSink(func(result interface{}) { - fmt.Printf(" 🚨 告警事件: %+v\n", result) - }) - - // 模拟传感器数据(包含异常值) - sensorData := []map[string]interface{}{ - {"deviceId": "sensor001", "temperature": 25.0, "humidity": 60, "location": "room1"}, // 正常 - {"deviceId": "sensor002", "temperature": 45.0, "humidity": 50, "location": "room2"}, // 高温告警 - {"deviceId": "sensor003", "temperature": 20.0, "humidity": 95, "location": "room3"}, // 高湿度告警 - {"deviceId": "sensor004", "temperature": 2.0, "humidity": 30, "location": "room4"}, // 低温告警 - {"deviceId": "sensor005", "temperature": 22.0, "humidity": 15, "location": "room5"}, // 低湿度告警 - {"deviceId": "sensor006", "temperature": 24.0, "humidity": 55, "location": "room6"}, // 正常 - } - - for _, data := range sensorData { - ssql.Stream().AddData(data) - time.Sleep(150 * time.Millisecond) - } - - time.Sleep(200 * time.Millisecond) -} - -// 场景4: 数据格式转换 -func demonstrateDataFormatConversion() { - ssql := streamsql.New() - defer ssql.Stop() - - // 格式转换SQL - rsql := `SELECT deviceId, - CONCAT('{"device_id":"', deviceId, '","metrics":{"temp":', - CAST(temperature AS STRING), ',"hum":', - CAST(humidity AS STRING), '},"location":"', - location, '","timestamp":', - CAST(NOW() AS STRING), '}') as json_format, - CONCAT(deviceId, '|', location, '|', - CAST(temperature AS STRING), '|', - CAST(humidity AS STRING)) as csv_format - FROM stream` - - err := ssql.Execute(rsql) - if err != nil { - panic(err) - } - - ssql.Stream().AddSink(func(result interface{}) { - fmt.Printf(" 格式转换结果: %+v\n", result) - }) - - // 输入数据 - inputData := []map[string]interface{}{ - {"deviceId": "sensor001", "temperature": 25.5, "humidity": 60, "location": "warehouse-A"}, - {"deviceId": "sensor002", "temperature": 22.0, "humidity": 55, "location": "warehouse-B"}, - } - - for _, data := range inputData { - ssql.Stream().AddData(data) - time.Sleep(100 * time.Millisecond) - } - - time.Sleep(200 * time.Millisecond) -} - -// 场景5: 基于条件的数据路由 -func demonstrateDataRouting() { - ssql := streamsql.New() - defer ssql.Stop() - - // 数据路由SQL - rsql := `SELECT *, - CASE WHEN deviceType = 'temperature' AND temperature > 30 THEN 'high_temp_topic' - WHEN deviceType = 'humidity' AND humidity > 80 THEN 'high_humidity_topic' - WHEN deviceType = 'pressure' THEN 'pressure_topic' - ELSE 'default_topic' END as routing_topic, - CASE WHEN temperature > 35 OR humidity > 85 THEN 'urgent' - WHEN temperature > 25 OR humidity > 70 THEN 'normal' - ELSE 'low' END as priority - FROM stream` - - err := ssql.Execute(rsql) - if err != nil { - panic(err) - } - - ssql.Stream().AddSink(func(result interface{}) { - fmt.Printf(" 路由结果: %+v\n", result) - }) - - // 不同类型的设备数据 - deviceData := []map[string]interface{}{ - {"deviceId": "temp001", "deviceType": "temperature", "temperature": 35.0, "humidity": 60}, - {"deviceId": "hum001", "deviceType": "humidity", "temperature": 25.0, "humidity": 85}, - {"deviceId": "press001", "deviceType": "pressure", "temperature": 22.0, "pressure": 1013.25}, - {"deviceId": "temp002", "deviceType": "temperature", "temperature": 20.0, "humidity": 50}, - } - - for _, data := range deviceData { - ssql.Stream().AddData(data) - time.Sleep(100 * time.Millisecond) - } - - time.Sleep(200 * time.Millisecond) -} - -// 场景6: 嵌套字段处理 -func demonstrateNestedFieldProcessing() { - ssql := streamsql.New() - defer ssql.Stop() - - // 嵌套字段处理SQL - rsql := `SELECT device.info.id as device_id, - device.info.name as device_name, - device.location.building as building, - device.location.room as room, - metrics.temperature as temp, - metrics.humidity as humidity, - CONCAT(device.location.building, '-', device.location.room, '-', device.info.id) as full_path, - CASE WHEN metrics.temperature > device.config.max_temp THEN 'OVER_LIMIT' - ELSE 'NORMAL' END as temp_status - FROM stream - WHERE device.info.type = 'sensor'` - - err := ssql.Execute(rsql) - if err != nil { - panic(err) - } - - ssql.Stream().AddSink(func(result interface{}) { - fmt.Printf(" 嵌套字段处理结果: %+v\n", result) - }) - - // 嵌套结构数据 - nestedData := []map[string]interface{}{ - { - "device": map[string]interface{}{ - "info": map[string]interface{}{ - "id": "sensor001", - "name": "Temperature Sensor 1", - "type": "sensor", - }, - "location": map[string]interface{}{ - "building": "Building-A", - "room": "Room-101", - }, - "config": map[string]interface{}{ - "max_temp": 30.0, - "min_temp": 10.0, - }, - }, - "metrics": map[string]interface{}{ - "temperature": 32.5, - "humidity": 65, - }, - }, - { - "device": map[string]interface{}{ - "info": map[string]interface{}{ - "id": "sensor002", - "name": "Humidity Sensor 1", - "type": "sensor", - }, - "location": map[string]interface{}{ - "building": "Building-B", - "room": "Room-201", - }, - "config": map[string]interface{}{ - "max_temp": 25.0, - "min_temp": 15.0, - }, - }, - "metrics": map[string]interface{}{ - "temperature": 22.0, - "humidity": 70, - }, - }, - } - - for _, data := range nestedData { - ssql.Stream().AddData(data) - time.Sleep(100 * time.Millisecond) - } - - time.Sleep(200 * time.Millisecond) -} - -// 生成随机测试数据的辅助函数 -func generateRandomSensorData(deviceId string) map[string]interface{} { - return map[string]interface{}{ - "deviceId": deviceId, - "temperature": 15.0 + rand.Float64()*25.0, // 15-40度 - "humidity": 30.0 + rand.Float64()*40.0, // 30-70% - "location": fmt.Sprintf("room%d", rand.Intn(10)+1), - "timestamp": time.Now().Unix(), - } -} +package main + +import ( + "fmt" + "math/rand" + "time" + + "github.com/rulego/streamsql" +) + +// 非聚合场景使用示例 +// 展示StreamSQL在实时数据转换、过滤、清洗等场景中的应用 +func main() { + fmt.Println("=== StreamSQL 非聚合场景演示 ===") + + // 场景1: 实时数据清洗和标准化 + fmt.Println("\n1. 实时数据清洗和标准化") + demonstrateDataCleaning() + + // 场景2: 数据富化和计算字段 + fmt.Println("\n2. 数据富化和计算字段") + demonstrateDataEnrichment() + + // 场景3: 实时告警和事件过滤 + fmt.Println("\n3. 实时告警和事件过滤") + demonstrateRealTimeAlerting() + + // 场景4: 数据格式转换 + fmt.Println("\n4. 数据格式转换") + demonstrateDataFormatConversion() + + // 场景5: 基于条件的数据路由 + fmt.Println("\n5. 基于条件的数据路由") + demonstrateDataRouting() + + // 场景6: 嵌套字段处理 + fmt.Println("\n6. 嵌套字段处理") + demonstrateNestedFieldProcessing() + + fmt.Println("\n=== 演示完成 ===") +} + +// 场景1: 实时数据清洗和标准化 +func demonstrateDataCleaning() { + ssql := streamsql.New() + defer ssql.Stop() + + // 清洗和标准化SQL + rsql := `SELECT deviceId, + UPPER(TRIM(deviceType)) as device_type, + ROUND(temperature, 2) as temperature, + COALESCE(location, 'unknown') as location, + CASE WHEN status = 1 THEN 'active' + WHEN status = 0 THEN 'inactive' + ELSE 'unknown' END as status_text + FROM stream + WHERE deviceId != '' AND temperature > -999` + + err := ssql.Execute(rsql) + if err != nil { + panic(err) + } + + // 结果处理 + ssql.AddSink(func(result interface{}) { + fmt.Printf(" 清洗后数据: %+v\n", result) + }) + + // 模拟脏数据输入 + dirtyData := []map[string]interface{}{ + {"deviceId": "sensor001", "deviceType": " temperature ", "temperature": 25.456789, "location": "room1", "status": 1}, + {"deviceId": "sensor002", "deviceType": "humidity", "temperature": 60.123, "location": nil, "status": 0}, + {"deviceId": "", "deviceType": "pressure", "temperature": nil, "location": "room2", "status": 2}, // 应被过滤 + {"deviceId": "sensor003", "deviceType": "TEMPERATURE", "temperature": 22.7, "location": "room3", "status": 1}, + } + + for _, data := range dirtyData { + ssql.Emit(data) + time.Sleep(50 * time.Millisecond) + } + + time.Sleep(200 * time.Millisecond) +} + +// 场景2: 数据富化和计算字段 +func demonstrateDataEnrichment() { + ssql := streamsql.New() + defer ssql.Stop() + + // 数据富化SQL + rsql := `SELECT *, + temperature * 1.8 + 32 as temp_fahrenheit, + CASE WHEN temperature > 30 THEN 'hot' + WHEN temperature < 15 THEN 'cold' + ELSE 'normal' END as temp_category, + CONCAT(location, '-', deviceId) as full_identifier, + NOW() as processed_timestamp, + ROUND(humidity / 100.0, 4) as humidity_ratio + FROM stream` + + err := ssql.Execute(rsql) + if err != nil { + panic(err) + } + + ssql.AddSink(func(result interface{}) { + fmt.Printf(" 富化后数据: %+v\n", result) + }) + + // 原始数据 + rawData := []map[string]interface{}{ + {"deviceId": "sensor001", "temperature": 32.5, "humidity": 65, "location": "greenhouse"}, + {"deviceId": "sensor002", "temperature": 12.0, "humidity": 45, "location": "warehouse"}, + {"deviceId": "sensor003", "temperature": 22.8, "humidity": 70, "location": "office"}, + } + + for _, data := range rawData { + ssql.Emit(data) + time.Sleep(100 * time.Millisecond) + } + + time.Sleep(200 * time.Millisecond) +} + +// 场景3: 实时告警和事件过滤 +func demonstrateRealTimeAlerting() { + ssql := streamsql.New() + defer ssql.Stop() + + // 告警过滤SQL + rsql := `SELECT deviceId, + temperature, + humidity, + location, + 'CRITICAL' as alert_level, + CASE WHEN temperature > 40 THEN 'High Temperature Alert' + WHEN temperature < 5 THEN 'Low Temperature Alert' + WHEN humidity > 90 THEN 'High Humidity Alert' + WHEN humidity < 20 THEN 'Low Humidity Alert' + ELSE 'Unknown Alert' END as alert_message, + NOW() as alert_time + FROM stream + WHERE temperature > 40 OR temperature < 5 OR humidity > 90 OR humidity < 20` + + err := ssql.Execute(rsql) + if err != nil { + panic(err) + } + + ssql.AddSink(func(result interface{}) { + fmt.Printf(" 🚨 告警事件: %+v\n", result) + }) + + // 模拟传感器数据(包含异常值) + sensorData := []map[string]interface{}{ + {"deviceId": "sensor001", "temperature": 25.0, "humidity": 60, "location": "room1"}, // 正常 + {"deviceId": "sensor002", "temperature": 45.0, "humidity": 50, "location": "room2"}, // 高温告警 + {"deviceId": "sensor003", "temperature": 20.0, "humidity": 95, "location": "room3"}, // 高湿度告警 + {"deviceId": "sensor004", "temperature": 2.0, "humidity": 30, "location": "room4"}, // 低温告警 + {"deviceId": "sensor005", "temperature": 22.0, "humidity": 15, "location": "room5"}, // 低湿度告警 + {"deviceId": "sensor006", "temperature": 24.0, "humidity": 55, "location": "room6"}, // 正常 + } + + for _, data := range sensorData { + ssql.Emit(data) + time.Sleep(150 * time.Millisecond) + } + + time.Sleep(200 * time.Millisecond) +} + +// 场景4: 数据格式转换 +func demonstrateDataFormatConversion() { + ssql := streamsql.New() + defer ssql.Stop() + + // 格式转换SQL + rsql := `SELECT deviceId, + CONCAT('{"device_id":"', deviceId, '","metrics":{"temp":', + CAST(temperature AS STRING), ',"hum":', + CAST(humidity AS STRING), '},"location":"', + location, '","timestamp":', + CAST(NOW() AS STRING), '}') as json_format, + CONCAT(deviceId, '|', location, '|', + CAST(temperature AS STRING), '|', + CAST(humidity AS STRING)) as csv_format + FROM stream` + + err := ssql.Execute(rsql) + if err != nil { + panic(err) + } + + ssql.AddSink(func(result interface{}) { + fmt.Printf(" 格式转换结果: %+v\n", result) + }) + + // 输入数据 + inputData := []map[string]interface{}{ + {"deviceId": "sensor001", "temperature": 25.5, "humidity": 60, "location": "warehouse-A"}, + {"deviceId": "sensor002", "temperature": 22.0, "humidity": 55, "location": "warehouse-B"}, + } + + for _, data := range inputData { + ssql.Emit(data) + time.Sleep(100 * time.Millisecond) + } + + time.Sleep(200 * time.Millisecond) +} + +// 场景5: 基于条件的数据路由 +func demonstrateDataRouting() { + ssql := streamsql.New() + defer ssql.Stop() + + // 数据路由SQL + rsql := `SELECT *, + CASE WHEN deviceType = 'temperature' AND temperature > 30 THEN 'high_temp_topic' + WHEN deviceType = 'humidity' AND humidity > 80 THEN 'high_humidity_topic' + WHEN deviceType = 'pressure' THEN 'pressure_topic' + ELSE 'default_topic' END as routing_topic, + CASE WHEN temperature > 35 OR humidity > 85 THEN 'urgent' + WHEN temperature > 25 OR humidity > 70 THEN 'normal' + ELSE 'low' END as priority + FROM stream` + + err := ssql.Execute(rsql) + if err != nil { + panic(err) + } + + ssql.AddSink(func(result interface{}) { + fmt.Printf(" 路由结果: %+v\n", result) + }) + + // 不同类型的设备数据 + deviceData := []map[string]interface{}{ + {"deviceId": "temp001", "deviceType": "temperature", "temperature": 35.0, "humidity": 60}, + {"deviceId": "hum001", "deviceType": "humidity", "temperature": 25.0, "humidity": 85}, + {"deviceId": "press001", "deviceType": "pressure", "temperature": 22.0, "pressure": 1013.25}, + {"deviceId": "temp002", "deviceType": "temperature", "temperature": 20.0, "humidity": 50}, + } + + for _, data := range deviceData { + ssql.Emit(data) + time.Sleep(100 * time.Millisecond) + } + + time.Sleep(200 * time.Millisecond) +} + +// 场景6: 嵌套字段处理 +func demonstrateNestedFieldProcessing() { + ssql := streamsql.New() + defer ssql.Stop() + + // 嵌套字段处理SQL + rsql := `SELECT device.info.id as device_id, + device.info.name as device_name, + device.location.building as building, + device.location.room as room, + metrics.temperature as temp, + metrics.humidity as humidity, + CONCAT(device.location.building, '-', device.location.room, '-', device.info.id) as full_path, + CASE WHEN metrics.temperature > device.config.max_temp THEN 'OVER_LIMIT' + ELSE 'NORMAL' END as temp_status + FROM stream + WHERE device.info.type = 'sensor'` + + err := ssql.Execute(rsql) + if err != nil { + panic(err) + } + + ssql.AddSink(func(result interface{}) { + fmt.Printf(" 嵌套字段处理结果: %+v\n", result) + }) + + // 嵌套结构数据 + nestedData := []map[string]interface{}{ + { + "device": map[string]interface{}{ + "info": map[string]interface{}{ + "id": "sensor001", + "name": "Temperature Sensor 1", + "type": "sensor", + }, + "location": map[string]interface{}{ + "building": "Building-A", + "room": "Room-101", + }, + "config": map[string]interface{}{ + "max_temp": 30.0, + "min_temp": 10.0, + }, + }, + "metrics": map[string]interface{}{ + "temperature": 32.5, + "humidity": 65, + }, + }, + { + "device": map[string]interface{}{ + "info": map[string]interface{}{ + "id": "sensor002", + "name": "Humidity Sensor 1", + "type": "sensor", + }, + "location": map[string]interface{}{ + "building": "Building-B", + "room": "Room-201", + }, + "config": map[string]interface{}{ + "max_temp": 25.0, + "min_temp": 15.0, + }, + }, + "metrics": map[string]interface{}{ + "temperature": 22.0, + "humidity": 70, + }, + }, + } + + for _, data := range nestedData { + ssql.Emit(data) + time.Sleep(100 * time.Millisecond) + } + + time.Sleep(200 * time.Millisecond) +} + +// 生成随机测试数据的辅助函数 +func generateRandomSensorData(deviceId string) map[string]interface{} { + return map[string]interface{}{ + "deviceId": deviceId, + "temperature": 15.0 + rand.Float64()*25.0, // 15-40度 + "humidity": 30.0 + rand.Float64()*40.0, // 30-70% + "location": fmt.Sprintf("room%d", rand.Intn(10)+1), + "timestamp": time.Now().Unix(), + } +} diff --git a/examples/null-comparison-examples/main.go b/examples/null-comparison-examples/main.go index 3d86102..cc09376 100644 --- a/examples/null-comparison-examples/main.go +++ b/examples/null-comparison-examples/main.go @@ -1,266 +1,266 @@ -package main - -import ( - "fmt" - "time" - - "github.com/rulego/streamsql" -) - -func main() { - fmt.Println("=== StreamSQL Null 比较语法演示 ===") - fmt.Println() - - demo1() // fieldName = nil 语法 - demo2() // fieldName != nil 语法 - demo3() // fieldName = null 和 != null 语法 - demo4() // 混合语法演示 - demo5() // 嵌套字段 null 比较 -} - -func demo1() { - fmt.Println("1. fieldName = nil 语法演示") - fmt.Println("-------------------------------------------") - - ssql := streamsql.New() - defer ssql.Stop() - - // 使用 = nil 语法查找空值 - rsql := `SELECT deviceId, value, status - FROM stream - WHERE value = nil` - - err := ssql.Execute(rsql) - if err != nil { - panic(err) - } - - ssql.Stream().AddSink(func(result interface{}) { - if results, ok := result.([]map[string]interface{}); ok { - for _, data := range results { - fmt.Printf("发现空值数据: %+v\n", data) - } - } - }) - - testData := []map[string]interface{}{ - {"deviceId": "sensor1", "value": 25.5, "status": "active"}, - {"deviceId": "sensor2", "value": nil, "status": "active"}, // 符合条件 - {"deviceId": "sensor3", "value": 30.0, "status": "inactive"}, - {"deviceId": "sensor4", "value": nil, "status": "error"}, // 符合条件 - } - - for _, data := range testData { - ssql.Stream().AddData(data) - } - - time.Sleep(300 * time.Millisecond) - fmt.Println() -} - -func demo2() { - fmt.Println("2. fieldName != nil 语法演示") - fmt.Println("-------------------------------------------") - - ssql := streamsql.New() - defer ssql.Stop() - - // 使用 != nil 语法查找非空值 - rsql := `SELECT deviceId, value, status - FROM stream - WHERE value != nil AND value > 20` - - err := ssql.Execute(rsql) - if err != nil { - panic(err) - } - - ssql.Stream().AddSink(func(result interface{}) { - if results, ok := result.([]map[string]interface{}); ok { - for _, data := range results { - fmt.Printf("发现有效数据: %+v\n", data) - } - } - }) - - testData := []map[string]interface{}{ - {"deviceId": "sensor1", "value": 25.5, "status": "active"}, // 符合条件 - {"deviceId": "sensor2", "value": nil, "status": "active"}, // 不符合(空值) - {"deviceId": "sensor3", "value": 15.0, "status": "inactive"}, // 不符合(值<=20) - {"deviceId": "sensor4", "value": 30.0, "status": "error"}, // 符合条件 - } - - for _, data := range testData { - ssql.Stream().AddData(data) - } - - time.Sleep(300 * time.Millisecond) - fmt.Println() -} - -func demo3() { - fmt.Println("3. fieldName = null 和 != null 语法演示") - fmt.Println("-------------------------------------------") - - ssql := streamsql.New() - defer ssql.Stop() - - // 使用 = null 和 != null 语法 - rsql := `SELECT deviceId, value, status - FROM stream - WHERE status != null OR value = null` - - err := ssql.Execute(rsql) - if err != nil { - panic(err) - } - - ssql.Stream().AddSink(func(result interface{}) { - if results, ok := result.([]map[string]interface{}); ok { - for _, data := range results { - status := data["status"] - value := data["value"] - if status != nil { - fmt.Printf("状态非空的数据: %+v\n", data) - } else if value == nil { - fmt.Printf("值为空的数据: %+v\n", data) - } - } - } - }) - - testData := []map[string]interface{}{ - {"deviceId": "sensor1", "value": 25.5, "status": "active"}, // 符合(status不为null) - {"deviceId": "sensor2", "value": nil, "status": nil}, // 符合(value为null) - {"deviceId": "sensor3", "value": 30.0, "status": "inactive"}, // 符合(status不为null) - {"deviceId": "sensor4", "value": nil, "status": "error"}, // 符合(两个条件都满足) - } - - for _, data := range testData { - ssql.Stream().AddData(data) - } - - time.Sleep(300 * time.Millisecond) - fmt.Println() -} - -func demo4() { - fmt.Println("4. 混合 null 比较语法演示") - fmt.Println("-------------------------------------------") - - ssql := streamsql.New() - defer ssql.Stop() - - // 混合使用 IS NULL、= nil、!= null 等语法 - rsql := `SELECT deviceId, value, status, priority - FROM stream - WHERE (value IS NOT NULL AND value > 20) OR - (status = nil AND priority != null)` - - err := ssql.Execute(rsql) - if err != nil { - panic(err) - } - - ssql.Stream().AddSink(func(result interface{}) { - if results, ok := result.([]map[string]interface{}); ok { - for _, data := range results { - value := data["value"] - status := data["status"] - priority := data["priority"] - - if value != nil && value.(float64) > 20 { - fmt.Printf("高值数据 (value > 20): %+v\n", data) - } else if status == nil && priority != nil { - fmt.Printf("状态异常但有优先级的数据: %+v\n", data) - } - } - } - }) - - testData := []map[string]interface{}{ - {"deviceId": "sensor1", "value": 25.0, "status": "active", "priority": "high"}, // 符合第一个条件 - {"deviceId": "sensor2", "value": 15.0, "status": "active", "priority": "low"}, // 不符合 - {"deviceId": "sensor3", "value": nil, "status": nil, "priority": "medium"}, // 符合第二个条件 - {"deviceId": "sensor4", "value": nil, "status": nil, "priority": nil}, // 不符合 - {"deviceId": "sensor5", "value": 30.0, "status": "inactive", "priority": nil}, // 符合第一个条件 - {"deviceId": "sensor6", "value": 10.0, "status": nil, "priority": "urgent"}, // 符合第二个条件 - } - - for _, data := range testData { - ssql.Stream().AddData(data) - } - - time.Sleep(300 * time.Millisecond) - fmt.Println() -} - -func demo5() { - fmt.Println("5. 嵌套字段 null 比较演示") - fmt.Println("-------------------------------------------") - - ssql := streamsql.New() - defer ssql.Stop() - - // 嵌套字段的 null 比较 - rsql := `SELECT deviceId, device.location - FROM stream - WHERE device.location != nil` - - err := ssql.Execute(rsql) - if err != nil { - panic(err) - } - - ssql.Stream().AddSink(func(result interface{}) { - if results, ok := result.([]map[string]interface{}); ok { - for _, data := range results { - fmt.Printf("有位置信息的设备: %+v\n", data) - } - } - }) - - testData := []map[string]interface{}{ - { - "deviceId": "sensor1", - "device": map[string]interface{}{ - "location": "warehouse-A", - }, - }, // 符合条件 - { - "deviceId": "sensor2", - "device": map[string]interface{}{ - "location": nil, - }, - }, // 不符合(location为nil) - { - "deviceId": "sensor3", - "device": map[string]interface{}{}, - }, // 不符合(location字段不存在) - { - "deviceId": "sensor4", - "device": map[string]interface{}{ - "location": "office-B", - }, - }, // 符合条件 - } - - for _, data := range testData { - ssql.Stream().AddData(data) - } - - time.Sleep(300 * time.Millisecond) - fmt.Println() - - fmt.Println("=== Null 比较语法演示完成 ===") - fmt.Println() - fmt.Println("支持的 null 比较语法:") - fmt.Println("- fieldName IS NULL") - fmt.Println("- fieldName IS NOT NULL") - fmt.Println("- fieldName = nil") - fmt.Println("- fieldName != nil") - fmt.Println("- fieldName = null") - fmt.Println("- fieldName != null") - fmt.Println("- device.field = nil (嵌套字段)") - fmt.Println("- device.field != nil (嵌套字段)") -} +package main + +import ( + "fmt" + "time" + + "github.com/rulego/streamsql" +) + +func main() { + fmt.Println("=== StreamSQL Null 比较语法演示 ===") + fmt.Println() + + demo1() // fieldName = nil 语法 + demo2() // fieldName != nil 语法 + demo3() // fieldName = null 和 != null 语法 + demo4() // 混合语法演示 + demo5() // 嵌套字段 null 比较 +} + +func demo1() { + fmt.Println("1. fieldName = nil 语法演示") + fmt.Println("-------------------------------------------") + + ssql := streamsql.New() + defer ssql.Stop() + + // 使用 = nil 语法查找空值 + rsql := `SELECT deviceId, value, status + FROM stream + WHERE value = nil` + + err := ssql.Execute(rsql) + if err != nil { + panic(err) + } + + ssql.AddSink(func(result interface{}) { + if results, ok := result.([]map[string]interface{}); ok { + for _, data := range results { + fmt.Printf("发现空值数据: %+v\n", data) + } + } + }) + + testData := []map[string]interface{}{ + {"deviceId": "sensor1", "value": 25.5, "status": "active"}, + {"deviceId": "sensor2", "value": nil, "status": "active"}, // 符合条件 + {"deviceId": "sensor3", "value": 30.0, "status": "inactive"}, + {"deviceId": "sensor4", "value": nil, "status": "error"}, // 符合条件 + } + + for _, data := range testData { + ssql.Emit(data) + } + + time.Sleep(300 * time.Millisecond) + fmt.Println() +} + +func demo2() { + fmt.Println("2. fieldName != nil 语法演示") + fmt.Println("-------------------------------------------") + + ssql := streamsql.New() + defer ssql.Stop() + + // 使用 != nil 语法查找非空值 + rsql := `SELECT deviceId, value, status + FROM stream + WHERE value != nil AND value > 20` + + err := ssql.Execute(rsql) + if err != nil { + panic(err) + } + + ssql.AddSink(func(result interface{}) { + if results, ok := result.([]map[string]interface{}); ok { + for _, data := range results { + fmt.Printf("发现有效数据: %+v\n", data) + } + } + }) + + testData := []map[string]interface{}{ + {"deviceId": "sensor1", "value": 25.5, "status": "active"}, // 符合条件 + {"deviceId": "sensor2", "value": nil, "status": "active"}, // 不符合(空值) + {"deviceId": "sensor3", "value": 15.0, "status": "inactive"}, // 不符合(值<=20) + {"deviceId": "sensor4", "value": 30.0, "status": "error"}, // 符合条件 + } + + for _, data := range testData { + ssql.Emit(data) + } + + time.Sleep(300 * time.Millisecond) + fmt.Println() +} + +func demo3() { + fmt.Println("3. fieldName = null 和 != null 语法演示") + fmt.Println("-------------------------------------------") + + ssql := streamsql.New() + defer ssql.Stop() + + // 使用 = null 和 != null 语法 + rsql := `SELECT deviceId, value, status + FROM stream + WHERE status != null OR value = null` + + err := ssql.Execute(rsql) + if err != nil { + panic(err) + } + + ssql.AddSink(func(result interface{}) { + if results, ok := result.([]map[string]interface{}); ok { + for _, data := range results { + status := data["status"] + value := data["value"] + if status != nil { + fmt.Printf("状态非空的数据: %+v\n", data) + } else if value == nil { + fmt.Printf("值为空的数据: %+v\n", data) + } + } + } + }) + + testData := []map[string]interface{}{ + {"deviceId": "sensor1", "value": 25.5, "status": "active"}, // 符合(status不为null) + {"deviceId": "sensor2", "value": nil, "status": nil}, // 符合(value为null) + {"deviceId": "sensor3", "value": 30.0, "status": "inactive"}, // 符合(status不为null) + {"deviceId": "sensor4", "value": nil, "status": "error"}, // 符合(两个条件都满足) + } + + for _, data := range testData { + ssql.Emit(data) + } + + time.Sleep(300 * time.Millisecond) + fmt.Println() +} + +func demo4() { + fmt.Println("4. 混合 null 比较语法演示") + fmt.Println("-------------------------------------------") + + ssql := streamsql.New() + defer ssql.Stop() + + // 混合使用 IS NULL、= nil、!= null 等语法 + rsql := `SELECT deviceId, value, status, priority + FROM stream + WHERE (value IS NOT NULL AND value > 20) OR + (status = nil AND priority != null)` + + err := ssql.Execute(rsql) + if err != nil { + panic(err) + } + + ssql.AddSink(func(result interface{}) { + if results, ok := result.([]map[string]interface{}); ok { + for _, data := range results { + value := data["value"] + status := data["status"] + priority := data["priority"] + + if value != nil && value.(float64) > 20 { + fmt.Printf("高值数据 (value > 20): %+v\n", data) + } else if status == nil && priority != nil { + fmt.Printf("状态异常但有优先级的数据: %+v\n", data) + } + } + } + }) + + testData := []map[string]interface{}{ + {"deviceId": "sensor1", "value": 25.0, "status": "active", "priority": "high"}, // 符合第一个条件 + {"deviceId": "sensor2", "value": 15.0, "status": "active", "priority": "low"}, // 不符合 + {"deviceId": "sensor3", "value": nil, "status": nil, "priority": "medium"}, // 符合第二个条件 + {"deviceId": "sensor4", "value": nil, "status": nil, "priority": nil}, // 不符合 + {"deviceId": "sensor5", "value": 30.0, "status": "inactive", "priority": nil}, // 符合第一个条件 + {"deviceId": "sensor6", "value": 10.0, "status": nil, "priority": "urgent"}, // 符合第二个条件 + } + + for _, data := range testData { + ssql.Emit(data) + } + + time.Sleep(300 * time.Millisecond) + fmt.Println() +} + +func demo5() { + fmt.Println("5. 嵌套字段 null 比较演示") + fmt.Println("-------------------------------------------") + + ssql := streamsql.New() + defer ssql.Stop() + + // 嵌套字段的 null 比较 + rsql := `SELECT deviceId, device.location + FROM stream + WHERE device.location != nil` + + err := ssql.Execute(rsql) + if err != nil { + panic(err) + } + + ssql.AddSink(func(result interface{}) { + if results, ok := result.([]map[string]interface{}); ok { + for _, data := range results { + fmt.Printf("有位置信息的设备: %+v\n", data) + } + } + }) + + testData := []map[string]interface{}{ + { + "deviceId": "sensor1", + "device": map[string]interface{}{ + "location": "warehouse-A", + }, + }, // 符合条件 + { + "deviceId": "sensor2", + "device": map[string]interface{}{ + "location": nil, + }, + }, // 不符合(location为nil) + { + "deviceId": "sensor3", + "device": map[string]interface{}{}, + }, // 不符合(location字段不存在) + { + "deviceId": "sensor4", + "device": map[string]interface{}{ + "location": "office-B", + }, + }, // 符合条件 + } + + for _, data := range testData { + ssql.Emit(data) + } + + time.Sleep(300 * time.Millisecond) + fmt.Println() + + fmt.Println("=== Null 比较语法演示完成 ===") + fmt.Println() + fmt.Println("支持的 null 比较语法:") + fmt.Println("- fieldName IS NULL") + fmt.Println("- fieldName IS NOT NULL") + fmt.Println("- fieldName = nil") + fmt.Println("- fieldName != nil") + fmt.Println("- fieldName = null") + fmt.Println("- fieldName != null") + fmt.Println("- device.field = nil (嵌套字段)") + fmt.Println("- device.field != nil (嵌套字段)") +} diff --git a/examples/persistence/main.go b/examples/persistence/main.go index 7e269c3..6fdeb4a 100644 --- a/examples/persistence/main.go +++ b/examples/persistence/main.go @@ -78,7 +78,7 @@ func testDataOverflowPersistence() { "id": i, "value": fmt.Sprintf("data_%d", i), } - stream.AddData(data) + stream.Emit(data) } duration := time.Since(start) diff --git a/examples/simple-custom-functions/main.go b/examples/simple-custom-functions/main.go index fa1fb91..674b75d 100644 --- a/examples/simple-custom-functions/main.go +++ b/examples/simple-custom-functions/main.go @@ -122,7 +122,7 @@ func testSimpleQuery(ssql *streamsql.Streamsql) { } // 添加结果监听器 - ssql.Stream().AddSink(func(result interface{}) { + ssql.AddSink(func(result interface{}) { fmt.Printf(" 📊 简单查询结果: %v\n", result) }) @@ -143,7 +143,7 @@ func testSimpleQuery(ssql *streamsql.Streamsql) { } for _, data := range testData { - ssql.AddData(data) + ssql.Emit(data) time.Sleep(200 * time.Millisecond) // 稍微延迟 } @@ -171,7 +171,7 @@ func testAggregateQuery(ssql *streamsql.Streamsql) { } // 添加结果监听器 - ssql.Stream().AddSink(func(result interface{}) { + ssql.AddSink(func(result interface{}) { fmt.Printf(" 📊 聚合查询结果: %v\n", result) }) @@ -198,7 +198,7 @@ func testAggregateQuery(ssql *streamsql.Streamsql) { } for _, data := range testData { - ssql.AddData(data) + ssql.Emit(data) } // 等待窗口触发 diff --git a/examples/unified_config/demo.go b/examples/unified_config/demo.go index 7afe75b..637a71c 100644 --- a/examples/unified_config/demo.go +++ b/examples/unified_config/demo.go @@ -1,218 +1,218 @@ -package main - -import ( - "fmt" - "log" - "strings" - "time" - - "github.com/rulego/streamsql/stream" - "github.com/rulego/streamsql/types" -) - -func main() { - fmt.Println("=== StreamSQL 统一配置系统演示 ===") - - // 1. 使用新的配置API创建默认配置Stream - fmt.Println("\n1. 默认配置Stream:") - defaultConfig := types.NewConfig() - defaultConfig.SimpleFields = []string{"temperature", "humidity", "location"} - - defaultStream, err := stream.NewStream(defaultConfig) - if err != nil { - log.Fatal(err) - } - printStreamStats("默认配置", defaultStream) - - // 2. 使用高性能预设配置 - fmt.Println("\n2. 高性能配置Stream:") - highPerfConfig := types.NewConfigWithPerformance(types.HighPerformanceConfig()) - highPerfConfig.SimpleFields = []string{"temperature", "humidity", "location"} - - highPerfStream, err := stream.NewStreamWithHighPerformance(highPerfConfig) - if err != nil { - log.Fatal(err) - } - printStreamStats("高性能配置", highPerfStream) - - // 3. 使用低延迟预设配置 - fmt.Println("\n3. 低延迟配置Stream:") - lowLatencyConfig := types.NewConfigWithPerformance(types.LowLatencyConfig()) - lowLatencyConfig.SimpleFields = []string{"temperature", "humidity", "location"} - - lowLatencyStream, err := stream.NewStreamWithLowLatency(lowLatencyConfig) - if err != nil { - log.Fatal(err) - } - printStreamStats("低延迟配置", lowLatencyStream) - - // 4. 使用零数据丢失预设配置 - fmt.Println("\n4. 零数据丢失配置Stream:") - zeroLossConfig := types.NewConfigWithPerformance(types.ZeroDataLossConfig()) - zeroLossConfig.SimpleFields = []string{"temperature", "humidity", "location"} - - zeroLossStream, err := stream.NewStreamWithZeroDataLoss(zeroLossConfig) - if err != nil { - log.Fatal(err) - } - printStreamStats("零数据丢失配置", zeroLossStream) - - // 5. 使用持久化预设配置 - fmt.Println("\n5. 持久化配置Stream:") - persistConfig := types.NewConfigWithPerformance(types.PersistencePerformanceConfig()) - persistConfig.SimpleFields = []string{"temperature", "humidity", "location"} - - persistStream, err := stream.NewStreamWithCustomPerformance(persistConfig, types.PersistencePerformanceConfig()) - if err != nil { - log.Fatal(err) - } - printStreamStats("持久化配置", persistStream) - - // 6. 创建完全自定义的配置 - fmt.Println("\n6. 自定义配置Stream:") - customPerfConfig := types.PerformanceConfig{ - BufferConfig: types.BufferConfig{ - DataChannelSize: 30000, - ResultChannelSize: 25000, - WindowOutputSize: 3000, - EnableDynamicResize: true, - MaxBufferSize: 200000, - UsageThreshold: 0.85, - }, - OverflowConfig: types.OverflowConfig{ - Strategy: "expand", - BlockTimeout: 15 * time.Second, - AllowDataLoss: false, - ExpansionConfig: types.ExpansionConfig{ - GrowthFactor: 2.0, - MinIncrement: 2000, - TriggerThreshold: 0.9, - ExpansionTimeout: 3 * time.Second, - }, - }, - WorkerConfig: types.WorkerConfig{ - SinkPoolSize: 800, - SinkWorkerCount: 12, - MaxRetryRoutines: 10, - }, - MonitoringConfig: types.MonitoringConfig{ - EnableMonitoring: true, - StatsUpdateInterval: 500 * time.Millisecond, - EnableDetailedStats: true, - WarningThresholds: types.WarningThresholds{ - DropRateWarning: 5.0, - DropRateCritical: 15.0, - BufferUsageWarning: 75.0, - BufferUsageCritical: 90.0, - }, - }, - } - - customConfig := types.NewConfigWithPerformance(customPerfConfig) - customConfig.SimpleFields = []string{"temperature", "humidity", "location"} - - customStream, err := stream.NewStreamWithCustomPerformance(customConfig, customPerfConfig) - if err != nil { - log.Fatal(err) - } - printStreamStats("自定义配置", customStream) - - // 7. 配置比较演示 - fmt.Println("\n7. 配置比较:") - compareConfigurations() - - // 8. 实时数据处理演示 - fmt.Println("\n8. 实时数据处理演示:") - demonstrateRealTimeProcessing(defaultStream) - - // 9. 窗口统一配置演示 - fmt.Println("\n9. 窗口统一配置演示:") - demonstrateWindowConfig() - - // 清理资源 - fmt.Println("\n10. 清理资源...") - defaultStream.Stop() - highPerfStream.Stop() - lowLatencyStream.Stop() - zeroLossStream.Stop() - persistStream.Stop() - customStream.Stop() - - fmt.Println("\n=== 演示完成 ===") -} - -func printStreamStats(name string, s *stream.Stream) { - stats := s.GetStats() - detailedStats := s.GetDetailedStats() - - fmt.Printf("【%s】统计信息:\n", name) - fmt.Printf(" 数据通道: %d/%d (使用率: %.1f%%)\n", - stats["data_chan_len"], stats["data_chan_cap"], - detailedStats["data_chan_usage"]) - fmt.Printf(" 结果通道: %d/%d (使用率: %.1f%%)\n", - stats["result_chan_len"], stats["result_chan_cap"], - detailedStats["result_chan_usage"]) - fmt.Printf(" 工作池: %d/%d (使用率: %.1f%%)\n", - stats["sink_pool_len"], stats["sink_pool_cap"], - detailedStats["sink_pool_usage"]) - fmt.Printf(" 性能等级: %s\n", detailedStats["performance_level"]) -} - -func compareConfigurations() { - configs := map[string]types.PerformanceConfig{ - "默认配置": types.DefaultPerformanceConfig(), - "高性能配置": types.HighPerformanceConfig(), - "低延迟配置": types.LowLatencyConfig(), - "零丢失配置": types.ZeroDataLossConfig(), - "持久化配置": types.PersistencePerformanceConfig(), - } - - fmt.Printf("%-12s %-10s %-10s %-10s %-10s %-15s\n", - "配置类型", "数据缓冲", "结果缓冲", "工作池", "工作线程", "溢出策略") - fmt.Println(strings.Repeat("-", 75)) - - for name, config := range configs { - fmt.Printf("%-12s %-10d %-10d %-10d %-10d %-15s\n", - name, - config.BufferConfig.DataChannelSize, - config.BufferConfig.ResultChannelSize, - config.WorkerConfig.SinkPoolSize, - config.WorkerConfig.SinkWorkerCount, - config.OverflowConfig.Strategy) - } -} - -func demonstrateRealTimeProcessing(s *stream.Stream) { - // 设置数据接收器 - s.AddSink(func(data interface{}) { - fmt.Printf(" 接收到处理结果: %v\n", data) - }) - - // 启动流处理 - s.Start() - - // 模拟发送数据 - for i := 0; i < 3; i++ { - data := map[string]interface{}{ - "temperature": 20.0 + float64(i)*2.5, - "humidity": 60.0 + float64(i)*5, - "location": fmt.Sprintf("sensor_%d", i+1), - "timestamp": time.Now().Unix(), - } - - fmt.Printf(" 发送数据: %v\n", data) - s.AddData(data) - time.Sleep(100 * time.Millisecond) - } - - // 等待处理完成 - time.Sleep(200 * time.Millisecond) - - // 显示最终统计 - finalStats := s.GetDetailedStats() - fmt.Printf(" 最终统计 - 输入: %d, 输出: %d, 丢弃: %d, 处理率: %.1f%%\n", - finalStats["basic_stats"].(map[string]int64)["input_count"], - finalStats["basic_stats"].(map[string]int64)["output_count"], - finalStats["basic_stats"].(map[string]int64)["dropped_count"], - finalStats["process_rate"]) -} +package main + +import ( + "fmt" + "log" + "strings" + "time" + + "github.com/rulego/streamsql/stream" + "github.com/rulego/streamsql/types" +) + +func main() { + fmt.Println("=== StreamSQL 统一配置系统演示 ===") + + // 1. 使用新的配置API创建默认配置Stream + fmt.Println("\n1. 默认配置Stream:") + defaultConfig := types.NewConfig() + defaultConfig.SimpleFields = []string{"temperature", "humidity", "location"} + + defaultStream, err := stream.NewStream(defaultConfig) + if err != nil { + log.Fatal(err) + } + printStreamStats("默认配置", defaultStream) + + // 2. 使用高性能预设配置 + fmt.Println("\n2. 高性能配置Stream:") + highPerfConfig := types.NewConfigWithPerformance(types.HighPerformanceConfig()) + highPerfConfig.SimpleFields = []string{"temperature", "humidity", "location"} + + highPerfStream, err := stream.NewStreamWithHighPerformance(highPerfConfig) + if err != nil { + log.Fatal(err) + } + printStreamStats("高性能配置", highPerfStream) + + // 3. 使用低延迟预设配置 + fmt.Println("\n3. 低延迟配置Stream:") + lowLatencyConfig := types.NewConfigWithPerformance(types.LowLatencyConfig()) + lowLatencyConfig.SimpleFields = []string{"temperature", "humidity", "location"} + + lowLatencyStream, err := stream.NewStreamWithLowLatency(lowLatencyConfig) + if err != nil { + log.Fatal(err) + } + printStreamStats("低延迟配置", lowLatencyStream) + + // 4. 使用零数据丢失预设配置 + fmt.Println("\n4. 零数据丢失配置Stream:") + zeroLossConfig := types.NewConfigWithPerformance(types.ZeroDataLossConfig()) + zeroLossConfig.SimpleFields = []string{"temperature", "humidity", "location"} + + zeroLossStream, err := stream.NewStreamWithZeroDataLoss(zeroLossConfig) + if err != nil { + log.Fatal(err) + } + printStreamStats("零数据丢失配置", zeroLossStream) + + // 5. 使用持久化预设配置 + fmt.Println("\n5. 持久化配置Stream:") + persistConfig := types.NewConfigWithPerformance(types.PersistencePerformanceConfig()) + persistConfig.SimpleFields = []string{"temperature", "humidity", "location"} + + persistStream, err := stream.NewStreamWithCustomPerformance(persistConfig, types.PersistencePerformanceConfig()) + if err != nil { + log.Fatal(err) + } + printStreamStats("持久化配置", persistStream) + + // 6. 创建完全自定义的配置 + fmt.Println("\n6. 自定义配置Stream:") + customPerfConfig := types.PerformanceConfig{ + BufferConfig: types.BufferConfig{ + DataChannelSize: 30000, + ResultChannelSize: 25000, + WindowOutputSize: 3000, + EnableDynamicResize: true, + MaxBufferSize: 200000, + UsageThreshold: 0.85, + }, + OverflowConfig: types.OverflowConfig{ + Strategy: "expand", + BlockTimeout: 15 * time.Second, + AllowDataLoss: false, + ExpansionConfig: types.ExpansionConfig{ + GrowthFactor: 2.0, + MinIncrement: 2000, + TriggerThreshold: 0.9, + ExpansionTimeout: 3 * time.Second, + }, + }, + WorkerConfig: types.WorkerConfig{ + SinkPoolSize: 800, + SinkWorkerCount: 12, + MaxRetryRoutines: 10, + }, + MonitoringConfig: types.MonitoringConfig{ + EnableMonitoring: true, + StatsUpdateInterval: 500 * time.Millisecond, + EnableDetailedStats: true, + WarningThresholds: types.WarningThresholds{ + DropRateWarning: 5.0, + DropRateCritical: 15.0, + BufferUsageWarning: 75.0, + BufferUsageCritical: 90.0, + }, + }, + } + + customConfig := types.NewConfigWithPerformance(customPerfConfig) + customConfig.SimpleFields = []string{"temperature", "humidity", "location"} + + customStream, err := stream.NewStreamWithCustomPerformance(customConfig, customPerfConfig) + if err != nil { + log.Fatal(err) + } + printStreamStats("自定义配置", customStream) + + // 7. 配置比较演示 + fmt.Println("\n7. 配置比较:") + compareConfigurations() + + // 8. 实时数据处理演示 + fmt.Println("\n8. 实时数据处理演示:") + demonstrateRealTimeProcessing(defaultStream) + + // 9. 窗口统一配置演示 + fmt.Println("\n9. 窗口统一配置演示:") + demonstrateWindowConfig() + + // 清理资源 + fmt.Println("\n10. 清理资源...") + defaultStream.Stop() + highPerfStream.Stop() + lowLatencyStream.Stop() + zeroLossStream.Stop() + persistStream.Stop() + customStream.Stop() + + fmt.Println("\n=== 演示完成 ===") +} + +func printStreamStats(name string, s *stream.Stream) { + stats := s.GetStats() + detailedStats := s.GetDetailedStats() + + fmt.Printf("【%s】统计信息:\n", name) + fmt.Printf(" 数据通道: %d/%d (使用率: %.1f%%)\n", + stats["data_chan_len"], stats["data_chan_cap"], + detailedStats["data_chan_usage"]) + fmt.Printf(" 结果通道: %d/%d (使用率: %.1f%%)\n", + stats["result_chan_len"], stats["result_chan_cap"], + detailedStats["result_chan_usage"]) + fmt.Printf(" 工作池: %d/%d (使用率: %.1f%%)\n", + stats["sink_pool_len"], stats["sink_pool_cap"], + detailedStats["sink_pool_usage"]) + fmt.Printf(" 性能等级: %s\n", detailedStats["performance_level"]) +} + +func compareConfigurations() { + configs := map[string]types.PerformanceConfig{ + "默认配置": types.DefaultPerformanceConfig(), + "高性能配置": types.HighPerformanceConfig(), + "低延迟配置": types.LowLatencyConfig(), + "零丢失配置": types.ZeroDataLossConfig(), + "持久化配置": types.PersistencePerformanceConfig(), + } + + fmt.Printf("%-12s %-10s %-10s %-10s %-10s %-15s\n", + "配置类型", "数据缓冲", "结果缓冲", "工作池", "工作线程", "溢出策略") + fmt.Println(strings.Repeat("-", 75)) + + for name, config := range configs { + fmt.Printf("%-12s %-10d %-10d %-10d %-10d %-15s\n", + name, + config.BufferConfig.DataChannelSize, + config.BufferConfig.ResultChannelSize, + config.WorkerConfig.SinkPoolSize, + config.WorkerConfig.SinkWorkerCount, + config.OverflowConfig.Strategy) + } +} + +func demonstrateRealTimeProcessing(s *stream.Stream) { + // 设置数据接收器 + s.AddSink(func(data interface{}) { + fmt.Printf(" 接收到处理结果: %v\n", data) + }) + + // 启动流处理 + s.Start() + + // 模拟发送数据 + for i := 0; i < 3; i++ { + data := map[string]interface{}{ + "temperature": 20.0 + float64(i)*2.5, + "humidity": 60.0 + float64(i)*5, + "location": fmt.Sprintf("sensor_%d", i+1), + "timestamp": time.Now().Unix(), + } + + fmt.Printf(" 发送数据: %v\n", data) + s.Emit(data) + time.Sleep(100 * time.Millisecond) + } + + // 等待处理完成 + time.Sleep(200 * time.Millisecond) + + // 显示最终统计 + finalStats := s.GetDetailedStats() + fmt.Printf(" 最终统计 - 输入: %d, 输出: %d, 丢弃: %d, 处理率: %.1f%%\n", + finalStats["basic_stats"].(map[string]int64)["input_count"], + finalStats["basic_stats"].(map[string]int64)["output_count"], + finalStats["basic_stats"].(map[string]int64)["dropped_count"], + finalStats["process_rate"]) +} diff --git a/examples/unified_config/window_config_demo.go b/examples/unified_config/window_config_demo.go index 722187f..81dfa89 100644 --- a/examples/unified_config/window_config_demo.go +++ b/examples/unified_config/window_config_demo.go @@ -1,74 +1,74 @@ -package main - -import ( - "fmt" - "time" - - "github.com/rulego/streamsql" - "github.com/rulego/streamsql/types" -) - -// demonstrateWindowConfig 演示窗口统一配置的使用 -func demonstrateWindowConfig() { - fmt.Println("=== 窗口统一配置演示 ===") - - // 1. 测试默认配置的窗口 - fmt.Println("\n1. 默认配置窗口测试") - testWindowWithConfig("默认配置", streamsql.New()) - - // 2. 测试高性能配置的窗口 - fmt.Println("\n2. 高性能配置窗口测试") - testWindowWithConfig("高性能配置", streamsql.New(streamsql.WithHighPerformance())) - - // 3. 测试低延迟配置的窗口 - fmt.Println("\n3. 低延迟配置窗口测试") - testWindowWithConfig("低延迟配置", streamsql.New(streamsql.WithLowLatency())) - - // 4. 测试自定义配置的窗口 - fmt.Println("\n4. 自定义配置窗口测试") - customConfig := types.DefaultPerformanceConfig() - customConfig.BufferConfig.WindowOutputSize = 2000 // 自定义窗口输出缓冲区大小 - testWindowWithConfig("自定义配置", streamsql.New(streamsql.WithCustomPerformance(customConfig))) - - fmt.Println("\n=== 窗口配置演示完成 ===") -} - -func testWindowWithConfig(configName string, ssql *streamsql.Streamsql) { - // 执行一个简单的滚动窗口查询 - sql := "SELECT deviceId, AVG(temperature) as avg_temp FROM stream GROUP BY deviceId, TumblingWindow('2s')" - - err := ssql.Execute(sql) - if err != nil { - fmt.Printf("❌ %s - 执行SQL失败: %v\n", configName, err) - return - } - - // 添加结果处理器 - stream := ssql.Stream() - if stream != nil { - stream.AddSink(func(result interface{}) { - fmt.Printf("📊 %s - 窗口结果: %v\n", configName, result) - }) - - // 发送测试数据 - for i := 0; i < 5; i++ { - data := map[string]interface{}{ - "deviceId": fmt.Sprintf("device_%d", i%2), - "temperature": 20.0 + float64(i), - "timestamp": time.Now(), - } - ssql.AddData(data) - } - - // 等待处理完成 - time.Sleep(3 * time.Second) - - // 获取统计信息 - stats := ssql.GetDetailedStats() - fmt.Printf("📈 %s - 统计信息: %v\n", configName, stats) - } - - // 停止流处理 - ssql.Stop() - fmt.Printf("✅ %s - 测试完成\n", configName) -} +package main + +import ( + "fmt" + "time" + + "github.com/rulego/streamsql" + "github.com/rulego/streamsql/types" +) + +// demonstrateWindowConfig 演示窗口统一配置的使用 +func demonstrateWindowConfig() { + fmt.Println("=== 窗口统一配置演示 ===") + + // 1. 测试默认配置的窗口 + fmt.Println("\n1. 默认配置窗口测试") + testWindowWithConfig("默认配置", streamsql.New()) + + // 2. 测试高性能配置的窗口 + fmt.Println("\n2. 高性能配置窗口测试") + testWindowWithConfig("高性能配置", streamsql.New(streamsql.WithHighPerformance())) + + // 3. 测试低延迟配置的窗口 + fmt.Println("\n3. 低延迟配置窗口测试") + testWindowWithConfig("低延迟配置", streamsql.New(streamsql.WithLowLatency())) + + // 4. 测试自定义配置的窗口 + fmt.Println("\n4. 自定义配置窗口测试") + customConfig := types.DefaultPerformanceConfig() + customConfig.BufferConfig.WindowOutputSize = 2000 // 自定义窗口输出缓冲区大小 + testWindowWithConfig("自定义配置", streamsql.New(streamsql.WithCustomPerformance(customConfig))) + + fmt.Println("\n=== 窗口配置演示完成 ===") +} + +func testWindowWithConfig(configName string, ssql *streamsql.Streamsql) { + // 执行一个简单的滚动窗口查询 + sql := "SELECT deviceId, AVG(temperature) as avg_temp FROM stream GROUP BY deviceId, TumblingWindow('2s')" + + err := ssql.Execute(sql) + if err != nil { + fmt.Printf("❌ %s - 执行SQL失败: %v\n", configName, err) + return + } + + // 添加结果处理器 + stream := ssql.Stream() + if stream != nil { + stream.AddSink(func(result interface{}) { + fmt.Printf("📊 %s - 窗口结果: %v\n", configName, result) + }) + + // 发送测试数据 + for i := 0; i < 5; i++ { + data := map[string]interface{}{ + "deviceId": fmt.Sprintf("device_%d", i%2), + "temperature": 20.0 + float64(i), + "timestamp": time.Now(), + } + ssql.Emit(data) + } + + // 等待处理完成 + time.Sleep(3 * time.Second) + + // 获取统计信息 + stats := ssql.GetDetailedStats() + fmt.Printf("📈 %s - 统计信息: %v\n", configName, stats) + } + + // 停止流处理 + ssql.Stop() + fmt.Printf("✅ %s - 测试完成\n", configName) +} diff --git a/stream/stream.go b/stream/stream.go index 5e17396..e811ced 100644 --- a/stream/stream.go +++ b/stream/stream.go @@ -1053,7 +1053,7 @@ func (s *Stream) smartSplitArgs(argsStr string) ([]string, error) { return args, nil } -func (s *Stream) AddData(data interface{}) { +func (s *Stream) Emit(data interface{}) { atomic.AddInt64(&s.inputCount, 1) // 性能优化:直接调用预编译的函数指针,避免switch判断 s.addDataFunc(data) diff --git a/stream/stream_test.go b/stream/stream_test.go index cda3049..ee3e5fb 100644 --- a/stream/stream_test.go +++ b/stream/stream_test.go @@ -55,7 +55,7 @@ func TestStreamProcess(t *testing.T) { } for _, data := range testData { - strm.AddData(data) + strm.Emit(data) } // 等待窗口关闭并触发结果 @@ -139,7 +139,7 @@ func TestStreamWithoutFilter(t *testing.T) { } for _, data := range testData { - strm.AddData(data) + strm.Emit(data) } // 捕获结果 @@ -235,7 +235,7 @@ func TestIncompleteStreamProcess(t *testing.T) { } for _, data := range testData { - strm.AddData(data) + strm.Emit(data) } // 等待窗口关闭并触发结果 @@ -323,7 +323,7 @@ func TestWindowSlotAgg(t *testing.T) { } for _, data := range testData { - strm.AddData(data) + strm.Emit(data) } // 捕获结果 @@ -492,7 +492,7 @@ func TestStreamWithPersistenceStrategy(t *testing.T) { "temperature": float64(20 + i), "timestamp": time.Now(), } - stream.AddData(data) + stream.Emit(data) } // 等待处理完成 @@ -546,7 +546,7 @@ func TestStreamPersistenceRecovery(t *testing.T) { } for _, data := range testData { - stream1.AddData(data) + stream1.Emit(data) } // 等待数据持久化 @@ -695,7 +695,7 @@ func TestStreamPersistencePerformance(t *testing.T) { "value": i, "data": fmt.Sprintf("performance_test_data_%d", i), } - stream.AddData(data) + stream.Emit(data) } elapsed := time.Since(start) @@ -795,7 +795,7 @@ func TestSelectStarWithExpressionFields(t *testing.T) { "age": 25, } - stream.AddData(testData) + stream.Emit(testData) // 等待处理完成 time.Sleep(100 * time.Millisecond) @@ -873,7 +873,7 @@ func TestSelectStarWithExpressionFieldsOverride(t *testing.T) { "status": "active", } - stream.AddData(testData) + stream.Emit(testData) // 等待处理完成 time.Sleep(100 * time.Millisecond) @@ -939,7 +939,7 @@ func TestSelectStarWithoutExpressionFields(t *testing.T) { "status": "inactive", } - stream.AddData(testData) + stream.Emit(testData) // 等待处理完成 time.Sleep(100 * time.Millisecond) diff --git a/streamsql.go b/streamsql.go index d28c6b6..62f716a 100644 --- a/streamsql.go +++ b/streamsql.go @@ -31,7 +31,7 @@ import ( // // ssql := streamsql.New() // err := ssql.Execute("SELECT AVG(temperature) FROM stream GROUP BY TumblingWindow('5s')") -// ssql.AddData(map[string]interface{}{"temperature": 25.5}) +// ssql.Emit(map[string]interface{}{"temperature": 25.5}) type Streamsql struct { stream *stream.Stream @@ -158,7 +158,7 @@ func (s *Streamsql) Execute(sql string) error { return nil } -// AddData 向流中添加一条数据记录。 +// Emit 向流中添加一条数据记录。 // 数据会根据已配置的SQL查询进行处理和聚合。 // // 支持的数据格式: @@ -171,7 +171,7 @@ func (s *Streamsql) Execute(sql string) error { // 示例: // // // 添加设备数据 -// ssql.AddData(map[string]interface{}{ +// ssql.Emit(map[string]interface{}{ // "deviceId": "sensor001", // "temperature": 25.5, // "humidity": 60.0, @@ -179,14 +179,14 @@ func (s *Streamsql) Execute(sql string) error { // }) // // // 添加用户行为数据 -// ssql.AddData(map[string]interface{}{ +// ssql.Emit(map[string]interface{}{ // "userId": "user123", // "action": "click", // "page": "/home", // }) -func (s *Streamsql) AddData(data interface{}) { +func (s *Streamsql) Emit(data interface{}) { if s.stream != nil { - s.stream.AddData(data) + s.stream.Emit(data) } } @@ -248,3 +248,76 @@ func (s *Streamsql) Stop() { s.stream.Stop() } } + +// AddSink 直接添加结果处理回调函数。 +// 这是对 Stream().AddSink() 的便捷封装,使API调用更简洁。 +// +// 参数: +// - sink: 结果处理函数,接收处理结果作为参数 +// +// 示例: +// +// // 直接添加结果处理 +// ssql.AddSink(func(result interface{}) { +// fmt.Printf("处理结果: %v\n", result) +// }) +// +// // 添加多个处理器 +// ssql.AddSink(func(result interface{}) { +// // 保存到数据库 +// saveToDatabase(result) +// }) +// ssql.AddSink(func(result interface{}) { +// // 发送到消息队列 +// sendToQueue(result) +// }) +func (s *Streamsql) AddSink(sink func(interface{})) { + if s.stream != nil { + s.stream.AddSink(sink) + } +} + +// Print 打印结果到控制台。 +// 这是一个便捷方法,自动添加一个打印结果的sink函数。 +// +// 示例: +// +// // 简单打印结果 +// ssql.Print() +// +// // 等价于: +// ssql.AddSink(func(result interface{}) { +// fmt.Printf("Ressult: %v\n", result) +// }) +func (s *Streamsql) Print() { + s.AddSink(func(result interface{}) { + fmt.Printf("Ressult: %v\n", result) + }) +} + +// ToChannel 返回结果通道,用于异步获取处理结果。 +// 通过此通道可以以非阻塞方式获取流处理结果。 +// +// 返回值: +// - <-chan interface{}: 只读的结果通道,如果未执行SQL则返回nil +// +// 示例: +// +// // 获取结果通道 +// resultChan := ssql.ToChannel() +// if resultChan != nil { +// go func() { +// for result := range resultChan { +// fmt.Printf("异步结果: %v\n", result) +// } +// }() +// } +// +// 注意: +// - 必须有消费者持续从通道读取数据,否则可能导致流处理阻塞 +func (s *Streamsql) ToChannel() <-chan interface{} { + if s.stream != nil { + return s.stream.GetResultsChan() + } + return nil +} diff --git a/streamsql_benchmark_test.go b/streamsql_benchmark_test.go index a765135..328ebcc 100644 --- a/streamsql_benchmark_test.go +++ b/streamsql_benchmark_test.go @@ -3,7 +3,6 @@ package streamsql import ( "context" "math/rand" - "runtime" "sync/atomic" "testing" "time" @@ -37,7 +36,6 @@ func BenchmarkStreamSQLCore(b *testing.B) { }, } - for _, tt := range tests { b.Run(tt.name, func(b *testing.B) { // 使用默认配置进行基准测试 @@ -52,7 +50,7 @@ func BenchmarkStreamSQLCore(b *testing.B) { var resultReceived int64 // 添加结果处理器 - ssql.Stream().AddSink(func(result interface{}) { + ssql.AddSink(func(result interface{}) { atomic.AddInt64(&resultReceived, 1) }) @@ -81,7 +79,7 @@ func BenchmarkStreamSQLCore(b *testing.B) { // 执行基准测试 start := time.Now() for i := 0; i < b.N; i++ { - ssql.AddData(testData[i%len(testData)]) + ssql.Emit(testData[i%len(testData)]) } inputDuration := time.Since(start) @@ -152,7 +150,7 @@ func BenchmarkConfigComparison(b *testing.B) { } var resultCount int64 - ssql.Stream().AddSink(func(result interface{}) { + ssql.AddSink(func(result interface{}) { atomic.AddInt64(&resultCount, 1) }) @@ -176,7 +174,7 @@ func BenchmarkConfigComparison(b *testing.B) { start := time.Now() for i := 0; i < b.N; i++ { - ssql.AddData(testData[i%len(testData)]) + ssql.Emit(testData[i%len(testData)]) } inputDuration := time.Since(start) @@ -238,7 +236,7 @@ func BenchmarkPureInput(b *testing.B) { start := time.Now() for i := 0; i < b.N; i++ { - ssql.AddData(data) + ssql.Emit(data) } b.StopTimer() @@ -324,7 +322,7 @@ func BenchmarkConfigurationComparison(b *testing.B) { var resultCount int64 // 添加轻量级sink - ssql.Stream().AddSink(func(result interface{}) { + ssql.AddSink(func(result interface{}) { atomic.AddInt64(&resultCount, 1) }) @@ -351,7 +349,7 @@ func BenchmarkConfigurationComparison(b *testing.B) { // 执行基准测试 start := time.Now() for i := 0; i < b.N; i++ { - ssql.AddData(testData[i%len(testData)]) + ssql.Emit(testData[i%len(testData)]) } inputDuration := time.Since(start) @@ -394,127 +392,127 @@ func BenchmarkConfigurationComparison(b *testing.B) { } // TestMemoryUsageComparison 内存使用对比测试 -func TestMemoryUsageComparison(t *testing.T) { - tests := []struct { - name string - setupFunc func() *Streamsql - description string - expectedMB float64 // 预期内存使用(MB) - }{ - { - name: "轻量配置", - setupFunc: func() *Streamsql { - return New(WithBufferSizes(5000, 5000, 250)) - }, - description: "5K数据 + 5K结果 + 250sink池", - expectedMB: 1.0, // 预期约1MB - }, - { - name: "默认配置(中等场景)", - setupFunc: func() *Streamsql { - return New() - }, - description: "20K数据 + 20K结果 + 800sink池", - expectedMB: 3.0, // 预期约3MB - }, - { - name: "高性能配置", - setupFunc: func() *Streamsql { - return New(WithHighPerformance()) - }, - description: "50K数据 + 50K结果 + 1Ksinki池", - expectedMB: 12.0, // 预期约12MB - }, - { - name: "超大缓冲配置", - setupFunc: func() *Streamsql { - return New(WithBufferSizes(100000, 100000, 2000)) - }, - description: "100K数据缓冲,100K结果缓冲,2Ksinki池", - expectedMB: 25.0, // 预期约25MB - }, - } - - sql := "SELECT deviceId, temperature FROM stream WHERE temperature > 20" - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - // 获取开始内存 - var startMem runtime.MemStats - runtime.GC() - runtime.ReadMemStats(&startMem) - - // 创建Stream - ssql := tt.setupFunc() - err := ssql.Execute(sql) - if err != nil { - t.Fatalf("SQL执行失败: %v", err) - } - - // 等待初始化完成 - time.Sleep(10 * time.Millisecond) - - // 获取创建后内存 - var afterCreateMem runtime.MemStats - runtime.GC() - runtime.ReadMemStats(&afterCreateMem) - - createUsage := float64(afterCreateMem.Alloc-startMem.Alloc) / 1024 / 1024 - - // 添加一些数据测试内存增长 - testData := generateTestData(3) - for i := 0; i < 1000; i++ { - ssql.AddData(testData[i%len(testData)]) - } - - time.Sleep(50 * time.Millisecond) - - // 获取使用后内存 - var afterUseMem runtime.MemStats - runtime.GC() - runtime.ReadMemStats(&afterUseMem) - - totalUsage := float64(afterUseMem.Alloc-startMem.Alloc) / 1024 / 1024 - - // 获取详细统计 - detailedStats := ssql.Stream().GetDetailedStats() - basicStats := detailedStats["basic_stats"].(map[string]int64) - - ssql.Stop() - - t.Logf("=== %s 内存使用分析 ===", tt.name) - t.Logf("配置: %s", tt.description) - t.Logf("创建开销: %.2f MB", createUsage) - t.Logf("总内存使用: %.2f MB", totalUsage) - t.Logf("缓冲区配置:") - t.Logf(" 数据通道: %d", basicStats["data_chan_cap"]) - t.Logf(" 结果通道: %d", basicStats["result_chan_cap"]) - t.Logf(" Sink池: %d", basicStats["sink_pool_cap"]) - - // 计算理论内存使用 (每个接口槽位约24字节) - dataChanMem := float64(basicStats["data_chan_cap"]) * 24 / 1024 / 1024 - resultChanMem := float64(basicStats["result_chan_cap"]) * 24 / 1024 / 1024 - sinkPoolMem := float64(basicStats["sink_pool_cap"]) * 8 / 1024 / 1024 // 函数指针 - - theoreticalMem := dataChanMem + resultChanMem + sinkPoolMem - - t.Logf("理论内存分配:") - t.Logf(" 数据通道: %.2f MB", dataChanMem) - t.Logf(" 结果通道: %.2f MB", resultChanMem) - t.Logf(" Sink池: %.2f MB", sinkPoolMem) - t.Logf(" 理论总计: %.2f MB", theoreticalMem) - - // 内存效率分析 - if totalUsage > tt.expectedMB*2 { - t.Logf("警告: 内存使用超过预期2倍 (%.2f MB > %.2f MB)", totalUsage, tt.expectedMB*2) - } else if totalUsage > tt.expectedMB*1.5 { - t.Logf("注意: 内存使用超过预期50%% (%.2f MB > %.2f MB)", totalUsage, tt.expectedMB*1.5) - } else { - t.Logf("✓ 内存使用在合理范围内 (%.2f MB)", totalUsage) - } - }) - } -} +//func TestMemoryUsageComparison(t *testing.T) { +// tests := []struct { +// name string +// setupFunc func() *Streamsql +// description string +// expectedMB float64 // 预期内存使用(MB) +// }{ +// { +// name: "轻量配置", +// setupFunc: func() *Streamsql { +// return New(WithBufferSizes(5000, 5000, 250)) +// }, +// description: "5K数据 + 5K结果 + 250sink池", +// expectedMB: 1.0, // 预期约1MB +// }, +// { +// name: "默认配置(中等场景)", +// setupFunc: func() *Streamsql { +// return New() +// }, +// description: "20K数据 + 20K结果 + 800sink池", +// expectedMB: 3.0, // 预期约3MB +// }, +// { +// name: "高性能配置", +// setupFunc: func() *Streamsql { +// return New(WithHighPerformance()) +// }, +// description: "50K数据 + 50K结果 + 1Ksinki池", +// expectedMB: 12.0, // 预期约12MB +// }, +// { +// name: "超大缓冲配置", +// setupFunc: func() *Streamsql { +// return New(WithBufferSizes(100000, 100000, 2000)) +// }, +// description: "100K数据缓冲,100K结果缓冲,2Ksinki池", +// expectedMB: 25.0, // 预期约25MB +// }, +// } +// +// sql := "SELECT deviceId, temperature FROM stream WHERE temperature > 20" +// +// for _, tt := range tests { +// t.Run(tt.name, func(t *testing.T) { +// // 获取开始内存 +// var startMem runtime.MemStats +// runtime.GC() +// runtime.ReadMemStats(&startMem) +// +// // 创建Stream +// ssql := tt.setupFunc() +// err := ssql.Execute(sql) +// if err != nil { +// t.Fatalf("SQL执行失败: %v", err) +// } +// +// // 等待初始化完成 +// time.Sleep(10 * time.Millisecond) +// +// // 获取创建后内存 +// var afterCreateMem runtime.MemStats +// runtime.GC() +// runtime.ReadMemStats(&afterCreateMem) +// +// createUsage := float64(afterCreateMem.Alloc-startMem.Alloc) / 1024 / 1024 +// +// // 添加一些数据测试内存增长 +// testData := generateTestData(3) +// for i := 0; i < 1000; i++ { +// ssql.Emit(testData[i%len(testData)]) +// } +// +// time.Sleep(50 * time.Millisecond) +// +// // 获取使用后内存 +// var afterUseMem runtime.MemStats +// runtime.GC() +// runtime.ReadMemStats(&afterUseMem) +// +// totalUsage := float64(afterUseMem.Alloc-startMem.Alloc) / 1024 / 1024 +// +// // 获取详细统计 +// detailedStats := ssql.Stream().GetDetailedStats() +// basicStats := detailedStats["basic_stats"].(map[string]int64) +// +// ssql.Stop() +// +// t.Logf("=== %s 内存使用分析 ===", tt.name) +// t.Logf("配置: %s", tt.description) +// t.Logf("创建开销: %.2f MB", createUsage) +// t.Logf("总内存使用: %.2f MB", totalUsage) +// t.Logf("缓冲区配置:") +// t.Logf(" 数据通道: %d", basicStats["data_chan_cap"]) +// t.Logf(" 结果通道: %d", basicStats["result_chan_cap"]) +// t.Logf(" Sink池: %d", basicStats["sink_pool_cap"]) +// +// // 计算理论内存使用 (每个接口槽位约24字节) +// dataChanMem := float64(basicStats["data_chan_cap"]) * 24 / 1024 / 1024 +// resultChanMem := float64(basicStats["result_chan_cap"]) * 24 / 1024 / 1024 +// sinkPoolMem := float64(basicStats["sink_pool_cap"]) * 8 / 1024 / 1024 // 函数指针 +// +// theoreticalMem := dataChanMem + resultChanMem + sinkPoolMem +// +// t.Logf("理论内存分配:") +// t.Logf(" 数据通道: %.2f MB", dataChanMem) +// t.Logf(" 结果通道: %.2f MB", resultChanMem) +// t.Logf(" Sink池: %.2f MB", sinkPoolMem) +// t.Logf(" 理论总计: %.2f MB", theoreticalMem) +// +// // 内存效率分析 +// if totalUsage > tt.expectedMB*2 { +// t.Logf("警告: 内存使用超过预期2倍 (%.2f MB > %.2f MB)", totalUsage, tt.expectedMB*2) +// } else if totalUsage > tt.expectedMB*1.5 { +// t.Logf("注意: 内存使用超过预期50%% (%.2f MB > %.2f MB)", totalUsage, tt.expectedMB*1.5) +// } else { +// t.Logf("✓ 内存使用在合理范围内 (%.2f MB)", totalUsage) +// } +// }) +// } +//} // BenchmarkLightweightVsDefaultComparison 轻量 vs 默认配置基准测试 func BenchmarkLightweightVsDefaultComparison(b *testing.B) { @@ -549,7 +547,7 @@ func BenchmarkLightweightVsDefaultComparison(b *testing.B) { } var resultCount int64 - ssql.Stream().AddSink(func(result interface{}) { + ssql.AddSink(func(result interface{}) { atomic.AddInt64(&resultCount, 1) }) @@ -572,7 +570,7 @@ func BenchmarkLightweightVsDefaultComparison(b *testing.B) { start := time.Now() for i := 0; i < b.N; i++ { - ssql.AddData(testData[i%len(testData)]) + ssql.Emit(testData[i%len(testData)]) } inputDuration := time.Since(start) @@ -643,7 +641,7 @@ func BenchmarkStreamSQLRealistic(b *testing.B) { var actualResultCount int64 // 测量实际的处理完成 - ssql.Stream().AddSink(func(result interface{}) { + ssql.AddSink(func(result interface{}) { atomic.AddInt64(&actualResultCount, 1) }) @@ -660,7 +658,7 @@ func BenchmarkStreamSQLRealistic(b *testing.B) { start := time.Now() for i := 0; i < maxIterations; i++ { // 直接使用AddData,如果系统处理不过来会自然阻塞或丢弃 - ssql.AddData(testData[i%len(testData)]) + ssql.Emit(testData[i%len(testData)]) atomic.AddInt64(&processedCount, 1) // 每100条数据稍微停顿,模拟真实的数据流 @@ -741,7 +739,7 @@ func BenchmarkPurePerformance(b *testing.B) { // 纯输入性能测试 for i := 0; i < b.N; i++ { - ssql.AddData(data) + ssql.Emit(data) } b.StopTimer() @@ -807,7 +805,7 @@ func BenchmarkEndToEndProcessing(b *testing.B) { resultChan := make(chan bool, currentBatchSize) // 设置sink来捕获结果 - ssql.Stream().AddSink(func(result interface{}) { + ssql.AddSink(func(result interface{}) { count := atomic.AddInt64(&resultsReceived, 1) if count <= int64(currentBatchSize) { resultChan <- true @@ -819,7 +817,7 @@ func BenchmarkEndToEndProcessing(b *testing.B) { // 输入数据 for i := 0; i < currentBatchSize; i++ { - ssql.AddData(testData[i%len(testData)]) + ssql.Emit(testData[i%len(testData)]) } // 等待所有结果处理完成(对于非聚合查询) @@ -883,7 +881,7 @@ func BenchmarkSustainedProcessing(b *testing.B) { var lastResultTime time.Time // 设置结果处理器 - ssql.Stream().AddSink(func(result interface{}) { + ssql.AddSink(func(result interface{}) { atomic.AddInt64(&processedResults, 1) lastResultTime = time.Now() }) @@ -895,7 +893,7 @@ func BenchmarkSustainedProcessing(b *testing.B) { // 持续输入数据 for i := 0; i < b.N; i++ { - ssql.AddData(testData[i%len(testData)]) + ssql.Emit(testData[i%len(testData)]) // 每1000条检查一次处理进度 if i > 0 && i%1000 == 0 { diff --git a/streamsql_case_test.go b/streamsql_case_test.go index 21b87b4..66c5978 100644 --- a/streamsql_case_test.go +++ b/streamsql_case_test.go @@ -50,7 +50,7 @@ func TestCaseExpressionInSQL(t *testing.T) { }) for _, data := range testData { - streamSQL.stream.AddData(data) + streamSQL.Emit(data) } // 等待处理 @@ -102,7 +102,7 @@ func TestCaseExpressionInAggregation(t *testing.T) { }) for _, data := range testData { - streamSQL.stream.AddData(data) + streamSQL.Emit(data) } // 等待窗口触发 @@ -274,7 +274,7 @@ func TestComplexCaseExpressionsInAggregation(t *testing.T) { }) for _, data := range tc.data { - streamSQL.stream.AddData(data) + streamSQL.Emit(data) } // 等待窗口触发 @@ -377,7 +377,7 @@ func TestCaseExpressionNonAggregated(t *testing.T) { // 添加测试数据 for _, data := range tt.testData { - strm.AddData(data) + strm.Emit(data) } // 捕获结果 @@ -482,7 +482,7 @@ func TestCaseExpressionAggregated(t *testing.T) { }) for _, data := range tt.testData { - strm.AddData(data) + strm.Emit(data) } // 使用带超时的等待机制 @@ -606,13 +606,13 @@ func TestCaseExpressionNullHandlingInAggregation(t *testing.T) { var results []map[string]interface{} resultChan := make(chan interface{}, 10) - ssql.Stream().AddSink(func(result interface{}) { + ssql.AddSink(func(result interface{}) { resultChan <- result }) // 添加测试数据 for _, data := range tc.testData { - ssql.Stream().AddData(data) + ssql.Stream().Emit(data) } // 等待窗口触发 @@ -777,7 +777,7 @@ func TestHavingWithCaseExpressionFunctional(t *testing.T) { }) for _, data := range testData { - streamSQL.stream.AddData(data) + streamSQL.Emit(data) } // 等待窗口触发 @@ -866,7 +866,7 @@ func TestNegativeNumberInSQL(t *testing.T) { // 添加测试数据 for _, data := range testData { - streamSQL.stream.AddData(data) + streamSQL.Emit(data) } // 等待处理 diff --git a/streamsql_custom_functions_test.go b/streamsql_custom_functions_test.go index 0fd7e4f..4f0f0d3 100644 --- a/streamsql_custom_functions_test.go +++ b/streamsql_custom_functions_test.go @@ -72,7 +72,7 @@ func TestCustomMathFunctions(t *testing.T) { // 创建结果接收通道 resultChan := make(chan interface{}, 10) - streamsql.Stream().AddSink(func(result interface{}) { + streamsql.AddSink(func(result interface{}) { resultChan <- result }) @@ -86,7 +86,7 @@ func TestCustomMathFunctions(t *testing.T) { "y2": 4.0, // 距离应该是5 } - streamsql.AddData(testData) + streamsql.Emit(testData) // 等待窗口触发 time.Sleep(1 * time.Second) @@ -179,7 +179,7 @@ func TestCustomStringFunctions(t *testing.T) { // 创建结果接收通道 resultChan := make(chan interface{}, 10) - streamsql.Stream().AddSink(func(result interface{}) { + streamsql.AddSink(func(result interface{}) { resultChan <- result }) @@ -189,7 +189,7 @@ func TestCustomStringFunctions(t *testing.T) { "metadata": `{"version":"1.0","type":"temperature"}`, } - streamsql.AddData(testData) + streamsql.Emit(testData) time.Sleep(200 * time.Millisecond) // 验证结果 @@ -318,7 +318,7 @@ func TestCustomAggregateFunctions(t *testing.T) { // 创建结果接收通道 resultChan := make(chan interface{}, 10) - streamsql.Stream().AddSink(func(result interface{}) { + streamsql.AddSink(func(result interface{}) { resultChan <- result }) @@ -331,7 +331,7 @@ func TestCustomAggregateFunctions(t *testing.T) { } for _, data := range testData { - streamsql.AddData(data) + streamsql.Emit(data) } time.Sleep(1 * time.Second) @@ -557,7 +557,7 @@ func TestCustomFunctionWithAggregation(t *testing.T) { // 创建结果接收通道 resultChan := make(chan interface{}, 10) - streamsql.Stream().AddSink(func(result interface{}) { + streamsql.AddSink(func(result interface{}) { resultChan <- result }) @@ -568,7 +568,7 @@ func TestCustomFunctionWithAggregation(t *testing.T) { } for _, data := range testData { - streamsql.AddData(data) + streamsql.Emit(data) } time.Sleep(1 * time.Second) diff --git a/streamsql_function_integration_test.go b/streamsql_function_integration_test.go index 88c222a..9c45246 100644 --- a/streamsql_function_integration_test.go +++ b/streamsql_function_integration_test.go @@ -32,7 +32,7 @@ func TestFunctionIntegrationNonAggregation(t *testing.T) { "temperature": -25.5, "humidity": 64.0, } - strm.AddData(testData) + strm.Emit(testData) // 等待结果 ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) @@ -77,7 +77,7 @@ func TestFunctionIntegrationNonAggregation(t *testing.T) { "device": "sensor01", "location": "ROOM_A", } - strm.AddData(testData) + strm.Emit(testData) // 等待结果 ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) @@ -120,7 +120,7 @@ func TestFunctionIntegrationNonAggregation(t *testing.T) { "temperature": 25.7, "humidity": 65.0, } - strm.AddData(testData) + strm.Emit(testData) // 等待结果 ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) @@ -162,7 +162,7 @@ func TestFunctionIntegrationNonAggregation(t *testing.T) { "device": "test-device", "timestamp": testTime, } - strm.AddData(testData) + strm.Emit(testData) // 等待结果 ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) @@ -204,7 +204,7 @@ func TestFunctionIntegrationNonAggregation(t *testing.T) { "device": "test-device", "metadata": `{"type": "temperature_sensor", "version": "1.0"}`, } - strm.AddData(testData) + strm.Emit(testData) // 等待结果 ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) @@ -253,7 +253,7 @@ func TestFunctionIntegrationAggregation(t *testing.T) { } for _, data := range testData { - strm.AddData(data) + strm.Emit(data) } // 等待窗口初始化 @@ -317,7 +317,7 @@ func TestFunctionIntegrationAggregation(t *testing.T) { } for _, data := range testData { - strm.AddData(data) + strm.Emit(data) } // 等待窗口初始化 @@ -368,7 +368,7 @@ func TestFunctionIntegrationAggregation(t *testing.T) { } for _, data := range testData { - strm.AddData(data) + strm.Emit(data) } // 等待窗口初始化 @@ -430,7 +430,7 @@ func TestFunctionIntegrationMixed(t *testing.T) { } for _, data := range testData { - strm.AddData(data) + strm.Emit(data) } // 等待窗口初始化 @@ -497,7 +497,7 @@ func TestFunctionIntegrationMixed(t *testing.T) { "device": "sensor1", "temperature": 25.7, } - strm.AddData(testData) + strm.Emit(testData) // 等待结果 ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) @@ -540,7 +540,7 @@ func TestFunctionIntegrationMixed(t *testing.T) { } for _, data := range testData { - strm.AddData(data) + strm.Emit(data) } // 等待窗口初始化 @@ -594,7 +594,7 @@ func TestNestedFunctionSupport(t *testing.T) { } for _, data := range testData { - strm.AddData(data) + strm.Emit(data) } // 等待窗口初始化 @@ -659,7 +659,7 @@ func TestNestedFunctionSupport(t *testing.T) { } for _, data := range testData { - strm.AddData(data) + strm.Emit(data) } // 等待窗口初始化 @@ -728,7 +728,7 @@ func TestNestedFunctionSupport(t *testing.T) { } for _, data := range testData { - strm.AddData(data) + strm.Emit(data) } // 等待窗口初始化 @@ -794,7 +794,7 @@ func TestNestedFunctionExecutionOrder(t *testing.T) { }) // 添加测试数据 - strm.AddData(map[string]interface{}{"device": "sensor1", "temperature": 25.67}) + strm.Emit(map[string]interface{}{"device": "sensor1", "temperature": 25.67}) // 等待结果 ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) @@ -834,7 +834,7 @@ func TestNestedFunctionExecutionOrder(t *testing.T) { }) // 添加测试数据 - strm.AddData(map[string]interface{}{"device": "sensor1"}) + strm.Emit(map[string]interface{}{"device": "sensor1"}) // 等待结果 ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) @@ -874,7 +874,7 @@ func TestNestedFunctionExecutionOrder(t *testing.T) { }) // 添加测试数据 - strm.AddData(map[string]interface{}{"device": "sensor1", "temperature": 16.0}) + strm.Emit(map[string]interface{}{"device": "sensor1", "temperature": 16.0}) // 等待结果 ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) @@ -921,7 +921,7 @@ func TestNestedFunctionExecutionOrder(t *testing.T) { } for _, data := range testData { - strm.AddData(data) + strm.Emit(data) } // 等待窗口初始化 @@ -966,7 +966,7 @@ func TestNestedFunctionExecutionOrder(t *testing.T) { }) // 添加测试数据 - strm.AddData(map[string]interface{}{"device": "sensor1", "created_at": "2023-12-25 15:30:45"}) + strm.Emit(map[string]interface{}{"device": "sensor1", "created_at": "2023-12-25 15:30:45"}) // 等待结果 ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) @@ -1006,7 +1006,7 @@ func TestNestedFunctionExecutionOrder(t *testing.T) { }) // 添加测试数据(不包含invalid_field) - strm.AddData(map[string]interface{}{"device": "sensor1", "temperature": 25.0}) + strm.Emit(map[string]interface{}{"device": "sensor1", "temperature": 25.0}) // 等待结果 ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) diff --git a/streamsql_is_null_test.go b/streamsql_is_null_test.go index c96a851..c31ab54 100644 --- a/streamsql_is_null_test.go +++ b/streamsql_is_null_test.go @@ -1,1033 +1,1033 @@ -package streamsql - -import ( - "fmt" - "testing" - "time" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -// TestIsNullOperatorInSQL 测试IS NULL和IS NOT NULL语法功能 -func TestIsNullOperatorInSQL(t *testing.T) { - testCases := []struct { - name string - sql string - testData []map[string]interface{} - expected []map[string]interface{} - }{ - { - name: "IS NULL测试", - sql: "SELECT deviceId, value FROM stream WHERE value IS NULL", - testData: []map[string]interface{}{ - {"deviceId": "sensor1", "value": 25.5}, - {"deviceId": "sensor2", "value": nil}, - {"deviceId": "sensor3", "value": 30.0}, - {"deviceId": "sensor4", "value": nil}, - }, - expected: []map[string]interface{}{ - {"deviceId": "sensor2", "value": nil}, - {"deviceId": "sensor4", "value": nil}, - }, - }, - { - name: "IS NOT NULL测试", - sql: "SELECT deviceId, value FROM stream WHERE value IS NOT NULL", - testData: []map[string]interface{}{ - {"deviceId": "sensor1", "value": 25.5}, - {"deviceId": "sensor2", "value": nil}, - {"deviceId": "sensor3", "value": 30.0}, - {"deviceId": "sensor4", "value": nil}, - }, - expected: []map[string]interface{}{ - {"deviceId": "sensor1", "value": 25.5}, - {"deviceId": "sensor3", "value": 30.0}, - }, - }, - { - name: "嵌套字段IS NULL测试", - sql: "SELECT deviceId, device.location FROM stream WHERE device.location IS NULL", - testData: []map[string]interface{}{ - { - "deviceId": "sensor1", - "device": map[string]interface{}{ - "location": "warehouse-A", - }, - }, - { - "deviceId": "sensor2", - "device": map[string]interface{}{ - "location": nil, - }, - }, - { - "deviceId": "sensor3", - "device": map[string]interface{}{}, - }, - }, - expected: []map[string]interface{}{ - {"deviceId": "sensor2", "device.location": nil}, - {"deviceId": "sensor3", "device.location": nil}, // 字段不存在也被认为是null - }, - }, - { - name: "组合条件 - IS NULL AND其他条件", - sql: "SELECT deviceId, value, status FROM stream WHERE value IS NULL AND status = 'active'", - testData: []map[string]interface{}{ - {"deviceId": "sensor1", "value": 25.5, "status": "active"}, - {"deviceId": "sensor2", "value": nil, "status": "active"}, - {"deviceId": "sensor3", "value": nil, "status": "inactive"}, - {"deviceId": "sensor4", "value": 30.0, "status": "active"}, - }, - expected: []map[string]interface{}{ - {"deviceId": "sensor2", "value": nil, "status": "active"}, - }, - }, - { - name: "组合条件 - IS NOT NULL OR其他条件", - sql: "SELECT deviceId, value, status FROM stream WHERE value IS NOT NULL OR status = 'error'", - testData: []map[string]interface{}{ - {"deviceId": "sensor1", "value": 25.5, "status": "active"}, - {"deviceId": "sensor2", "value": nil, "status": "active"}, - {"deviceId": "sensor3", "value": nil, "status": "error"}, - {"deviceId": "sensor4", "value": 30.0, "status": "inactive"}, - }, - expected: []map[string]interface{}{ - {"deviceId": "sensor1", "value": 25.5, "status": "active"}, - {"deviceId": "sensor3", "value": nil, "status": "error"}, - {"deviceId": "sensor4", "value": 30.0, "status": "inactive"}, - }, - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - // 创建StreamSQL实例 - ssql := New() - defer ssql.Stop() - - // 执行SQL - err := ssql.Execute(tc.sql) - require.NoError(t, err) - - // 收集结果 - var results []map[string]interface{} - resultChan := make(chan interface{}, 10) - - ssql.Stream().AddSink(func(result interface{}) { - resultChan <- result - }) - - // 使用一个done channel来同步 - done := make(chan bool, 1) - - // 添加测试数据 - for _, data := range tc.testData { - ssql.Stream().AddData(data) - } - - // 在另一个goroutine中收集结果 - go func() { - defer func() { done <- true }() - // 等待一段时间收集结果 - timeout := time.After(300 * time.Millisecond) - for { - select { - case result := <-resultChan: - if resultSlice, ok := result.([]map[string]interface{}); ok { - results = append(results, resultSlice...) - } - case <-timeout: - return - } - } - }() - - // 等待收集完成 - <-done - - // 验证结果数量 - assert.Len(t, results, len(tc.expected), "结果数量应该匹配") - - // 验证结果内容(不依赖顺序) - expectedDeviceIds := make([]string, len(tc.expected)) - for i, exp := range tc.expected { - expectedDeviceIds[i] = exp["deviceId"].(string) - } - - actualDeviceIds := make([]string, len(results)) - for i, result := range results { - actualDeviceIds[i] = result["deviceId"].(string) - } - - // 验证每个期望的设备ID都在结果中 - for _, expectedId := range expectedDeviceIds { - assert.Contains(t, actualDeviceIds, expectedId, "结果应该包含设备ID %s", expectedId) - } - - // 验证每个结果的字段值 - for _, result := range results { - deviceId := result["deviceId"].(string) - // 找到对应的期望结果 - var expectedResult map[string]interface{} - for _, exp := range tc.expected { - if exp["deviceId"].(string) == deviceId { - expectedResult = exp - break - } - } - - if expectedResult != nil { - for key, expectedValue := range expectedResult { - actualValue := result[key] - assert.Equal(t, expectedValue, actualValue, - "设备 %s 的字段 %s 值应该匹配: 期望 %v, 实际 %v", deviceId, key, expectedValue, actualValue) - } - } - } - }) - } -} - -// TestIsNullInAggregation 测试聚合查询中的IS NULL -func TestIsNullInAggregation(t *testing.T) { - ssql := New() - defer ssql.Stop() - - // 聚合查询:统计非空值的数量 - sql := `SELECT deviceType, - COUNT(*) as total_count, - COUNT(value) as non_null_count - FROM stream - WHERE value IS NOT NULL - GROUP BY deviceType, TumblingWindow('2s')` - - err := ssql.Execute(sql) - require.NoError(t, err) - - // 收集结果 - resultChan := make(chan interface{}, 10) - ssql.Stream().AddSink(func(result interface{}) { - resultChan <- result - }) - - // 添加测试数据 - testData := []map[string]interface{}{ - {"deviceType": "temperature", "value": 25.5}, - {"deviceType": "temperature", "value": nil}, - {"deviceType": "temperature", "value": 27.0}, - {"deviceType": "humidity", "value": 60.0}, - {"deviceType": "humidity", "value": nil}, - } - - for _, data := range testData { - ssql.Stream().AddData(data) - } - - // 等待窗口触发 - time.Sleep(3 * time.Second) - - // 验证结果 - select { - case result := <-resultChan: - resultSlice, ok := result.([]map[string]interface{}) - require.True(t, ok, "结果应该是[]map[string]interface{}类型") - - // 应该有temperature和humidity两种类型的结果 - assert.GreaterOrEqual(t, len(resultSlice), 1, "应该至少有一个聚合结果") - - for _, item := range resultSlice { - deviceType := item["deviceType"] - totalCount, _ := item["total_count"].(float64) - nonNullCount, _ := item["non_null_count"].(float64) - - if deviceType == "temperature" { - // temperature有2个非空值(25.5, 27.0) - assert.Equal(t, 2.0, totalCount, "temperature总数应该是2") - assert.Equal(t, 2.0, nonNullCount, "temperature非空数应该是2") - } else if deviceType == "humidity" { - // humidity有1个非空值(60.0) - assert.Equal(t, 1.0, totalCount, "humidity总数应该是1") - assert.Equal(t, 1.0, nonNullCount, "humidity非空数应该是1") - } - } - case <-time.After(5 * time.Second): - t.Fatal("测试超时,未收到聚合结果") - } -} - -// TestIsNullInHaving 测试HAVING子句中真正的IS NULL功能 -func TestIsNullInHaving(t *testing.T) { - ssql := New() - defer ssql.Stop() - - // 测试HAVING子句中的IS NULL:只返回平均值为NULL的设备类型 - sql := `SELECT deviceType, - COUNT(*) as total_count, - AVG(value) as avg_value - FROM stream - GROUP BY deviceType, TumblingWindow('2s') - HAVING avg_value IS NULL` - - err := ssql.Execute(sql) - require.NoError(t, err) - - resultChan := make(chan interface{}, 10) - ssql.Stream().AddSink(func(result interface{}) { - resultChan <- result - }) - - // 添加测试数据:只给pressure设备类型添加null值,这样它的平均值会是null - testData := []map[string]interface{}{ - {"deviceType": "temperature", "value": 25.0}, - {"deviceType": "temperature", "value": 27.0}, // temperature有值,平均值不为null - {"deviceType": "humidity", "value": 60.0}, // humidity有值,平均值不为null - {"deviceType": "pressure", "value": nil}, // pressure只有null值 - {"deviceType": "pressure", "value": nil}, // pressure再次null值,平均值会是null - } - - for _, data := range testData { - ssql.Stream().AddData(data) - } - - // 等待窗口触发 - time.Sleep(3 * time.Second) - - // 验证结果 - select { - case result := <-resultChan: - resultSlice, ok := result.([]map[string]interface{}) - require.True(t, ok, "结果应该是[]map[string]interface{}类型") - - // 应该只有pressure类型的结果(平均值为null) - assert.Len(t, resultSlice, 1, "应该只有一个结果") - - if len(resultSlice) > 0 { - item := resultSlice[0] - assert.Equal(t, "pressure", item["deviceType"], "应该是pressure类型") - - // 验证avg_value确实为null - avgValue := item["avg_value"] - assert.Nil(t, avgValue, "pressure的平均值应该是null") - - // 验证total_count - totalCount, ok := item["total_count"].(float64) - assert.True(t, ok, "total_count应该是float64类型") - assert.Equal(t, 2.0, totalCount, "pressure应该有2条记录") - } - - case <-time.After(5 * time.Second): - t.Fatal("测试超时,未收到聚合结果") - } -} - -// TestIsNullInHavingWithIsNotNull 测试HAVING子句中的IS NOT NULL功能 -func TestIsNullInHavingWithIsNotNull(t *testing.T) { - ssql := New() - defer ssql.Stop() - - // 测试HAVING子句中的IS NOT NULL:只返回平均值不为NULL的设备类型 - sql := `SELECT deviceType, - COUNT(*) as total_count, - AVG(value) as avg_value - FROM stream - GROUP BY deviceType, TumblingWindow('2s') - HAVING avg_value IS NOT NULL` - - err := ssql.Execute(sql) - require.NoError(t, err) - - resultChan := make(chan interface{}, 10) - ssql.Stream().AddSink(func(result interface{}) { - resultChan <- result - }) - - // 添加测试数据 - testData := []map[string]interface{}{ - {"deviceType": "temperature", "value": 25.0}, - {"deviceType": "temperature", "value": 27.0}, // temperature有值,平均值不为null - {"deviceType": "humidity", "value": 60.0}, // humidity有值,平均值不为null - {"deviceType": "pressure", "value": nil}, // pressure只有null值,平均值会是null - {"deviceType": "pressure", "value": nil}, - } - - for _, data := range testData { - ssql.Stream().AddData(data) - } - - // 等待窗口触发 - time.Sleep(3 * time.Second) - - // 验证结果 - select { - case result := <-resultChan: - resultSlice, ok := result.([]map[string]interface{}) - require.True(t, ok, "结果应该是[]map[string]interface{}类型") - - // 应该有temperature和humidity两种类型的结果(平均值不为null) - assert.Len(t, resultSlice, 2, "应该有两个结果") - - foundTypes := make([]string, 0) - for _, item := range resultSlice { - deviceType, ok := item["deviceType"].(string) - require.True(t, ok, "deviceType应该是string类型") - - // 验证avg_value不为null - avgValue := item["avg_value"] - assert.NotNil(t, avgValue, fmt.Sprintf("%s的平均值应该不为null", deviceType)) - - foundTypes = append(foundTypes, deviceType) - } - - // 验证包含temperature和humidity,不包含pressure - assert.Contains(t, foundTypes, "temperature", "结果应该包含temperature") - assert.Contains(t, foundTypes, "humidity", "结果应该包含humidity") - assert.NotContains(t, foundTypes, "pressure", "结果不应该包含pressure") - - case <-time.After(5 * time.Second): - t.Fatal("测试超时,未收到聚合结果") - } -} - -// TestIsNullWithOtherOperators 测试IS NULL与其他操作符的组合 -func TestIsNullWithOtherOperators(t *testing.T) { - ssql := New() - defer ssql.Stop() - - // 测试复杂的WHERE条件 - sql := `SELECT deviceId, value, status, location - FROM stream - WHERE (value IS NOT NULL AND value > 20) OR - (status IS NULL AND location LIKE 'warehouse%')` - - err := ssql.Execute(sql) - require.NoError(t, err) - - resultChan := make(chan interface{}, 10) - ssql.Stream().AddSink(func(result interface{}) { - resultChan <- result - }) - - // 添加测试数据 - testData := []map[string]interface{}{ - {"deviceId": "sensor1", "value": 25.0, "status": "active", "location": "warehouse-A"}, // 满足第一个条件 - {"deviceId": "sensor2", "value": 15.0, "status": "active", "location": "warehouse-B"}, // 不满足条件 - {"deviceId": "sensor3", "value": nil, "status": nil, "location": "warehouse-C"}, // 满足第二个条件 - {"deviceId": "sensor4", "value": nil, "status": "inactive", "location": "warehouse-D"}, // 不满足条件 - {"deviceId": "sensor5", "value": 30.0, "status": nil, "location": "office-A"}, // 满足第一个条件 - } - - for _, data := range testData { - ssql.Stream().AddData(data) - } - - // 使用超时方式安全收集结果 - var results []map[string]interface{} - timeout := time.After(500 * time.Millisecond) - -collecting: - for { - select { - case result := <-resultChan: - if resultSlice, ok := result.([]map[string]interface{}); ok { - results = append(results, resultSlice...) - } - case <-timeout: - break collecting - } - } - - // 验证结果:应该有sensor1, sensor3, sensor5 - assert.Len(t, results, 3, "应该有3个结果") - - expectedDeviceIds := []string{"sensor1", "sensor3", "sensor5"} - actualDeviceIds := make([]string, len(results)) - for i, result := range results { - actualDeviceIds[i] = result["deviceId"].(string) - } - - for _, expectedId := range expectedDeviceIds { - assert.Contains(t, actualDeviceIds, expectedId, "结果应该包含设备ID %s", expectedId) - } -} - -// TestCaseWhenWithIsNull 测试CASE WHEN表达式中使用IS NULL和IS NOT NULL -func TestCaseWhenWithIsNull(t *testing.T) { - testCases := []struct { - name string - sql string - testData []map[string]interface{} - expected []map[string]interface{} - }{ - { - name: "CASE WHEN IS NULL基本测试", - sql: `SELECT deviceId, - CASE WHEN status IS NULL THEN 0 - WHEN status IS NOT NULL THEN 1 - ELSE 2 END as status_flag - FROM stream`, - testData: []map[string]interface{}{ - {"deviceId": "sensor1", "status": "active"}, - {"deviceId": "sensor2", "status": nil}, - {"deviceId": "sensor3", "status": "inactive"}, - {"deviceId": "sensor4"}, // 没有status字段 - }, - expected: []map[string]interface{}{ - {"deviceId": "sensor1", "status_flag": 1.0}, - {"deviceId": "sensor2", "status_flag": 0.0}, - {"deviceId": "sensor3", "status_flag": 1.0}, - {"deviceId": "sensor4", "status_flag": 0.0}, - }, - }, - { - name: "CASE WHEN IS NOT NULL复杂条件测试", - sql: `SELECT deviceId, - CASE WHEN temperature IS NOT NULL AND temperature > 25 THEN 2 - WHEN temperature IS NOT NULL AND temperature <= 25 THEN 1 - WHEN temperature IS NULL THEN 0 - ELSE 3 END as temp_level - FROM stream`, - testData: []map[string]interface{}{ - {"deviceId": "sensor1", "temperature": 30.0}, - {"deviceId": "sensor2", "temperature": 20.0}, - {"deviceId": "sensor3", "temperature": nil}, - {"deviceId": "sensor4"}, // 没有temperature字段 - }, - expected: []map[string]interface{}{ - {"deviceId": "sensor1", "temp_level": 2.0}, - {"deviceId": "sensor2", "temp_level": 1.0}, - {"deviceId": "sensor3", "temp_level": 0.0}, - {"deviceId": "sensor4", "temp_level": 0.0}, - }, - }, - { - name: "多个CASE WHEN IS NULL条件测试", - sql: `SELECT deviceId, - CASE WHEN status IS NULL AND temperature IS NULL THEN 0 - WHEN status IS NULL AND temperature IS NOT NULL THEN 1 - WHEN status IS NOT NULL AND temperature IS NULL THEN 2 - WHEN status IS NOT NULL AND temperature IS NOT NULL THEN 3 - ELSE 4 END as combined_flag - FROM stream`, - testData: []map[string]interface{}{ - {"deviceId": "sensor1", "status": "active", "temperature": 25.0}, - {"deviceId": "sensor2", "status": "active", "temperature": nil}, - {"deviceId": "sensor3", "status": nil, "temperature": 30.0}, - {"deviceId": "sensor4", "status": nil, "temperature": nil}, - {"deviceId": "sensor5"}, // 两个字段都不存在 - }, - expected: []map[string]interface{}{ - {"deviceId": "sensor1", "combined_flag": 3.0}, - {"deviceId": "sensor2", "combined_flag": 2.0}, - {"deviceId": "sensor3", "combined_flag": 1.0}, - {"deviceId": "sensor4", "combined_flag": 0.0}, - {"deviceId": "sensor5", "combined_flag": 0.0}, - }, - }, - { - name: "CASE WHEN IS NULL与聚合函数结合测试", - sql: `SELECT deviceType, - COUNT(*) as total_count, - SUM(CASE WHEN value IS NULL THEN 1 ELSE 0 END) as null_count, - SUM(CASE WHEN value IS NOT NULL THEN 1 ELSE 0 END) as non_null_count - FROM stream - GROUP BY deviceType, TumblingWindow('2s')`, - testData: []map[string]interface{}{ - {"deviceType": "temperature", "value": 25.0}, - {"deviceType": "temperature", "value": nil}, - {"deviceType": "temperature", "value": 27.0}, - {"deviceType": "humidity", "value": 60.0}, - {"deviceType": "humidity", "value": nil}, - {"deviceType": "humidity", "value": nil}, - }, - expected: []map[string]interface{}{ - { - "deviceType": "temperature", - "total_count": 3.0, - "null_count": 1.0, - "non_null_count": 2.0, - }, - { - "deviceType": "humidity", - "total_count": 3.0, - "null_count": 2.0, - "non_null_count": 1.0, - }, - }, - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - // 创建StreamSQL实例 - ssql := New() - defer ssql.Stop() - - // 执行SQL - err := ssql.Execute(tc.sql) - require.NoError(t, err) - - // 收集结果 - var results []map[string]interface{} - resultChan := make(chan interface{}, 10) - - ssql.Stream().AddSink(func(result interface{}) { - resultChan <- result - }) - - // 添加测试数据 - for _, data := range tc.testData { - ssql.Stream().AddData(data) - } - - // 使用超时方式安全收集结果 - var timeout time.Duration - if tc.name == "CASE WHEN IS NULL与聚合函数结合测试" { - timeout = 4 * time.Second // 聚合查询需要更长时间 - } else { - timeout = 500 * time.Millisecond - } - - timeoutChan := time.After(timeout) - - collecting: - for { - select { - case result := <-resultChan: - if resultSlice, ok := result.([]map[string]interface{}); ok { - results = append(results, resultSlice...) - } - case <-timeoutChan: - break collecting - } - } - - // 验证结果数量 - assert.Len(t, results, len(tc.expected), "结果数量应该匹配") - - // 对于聚合查询,验证逻辑略有不同 - if tc.name == "CASE WHEN IS NULL与聚合函数结合测试" { - // 验证每个deviceType的结果 - for _, expectedResult := range tc.expected { - expectedDeviceType := expectedResult["deviceType"].(string) - - // 在结果中找到对应的deviceType - var actualResult map[string]interface{} - for _, result := range results { - if result["deviceType"].(string) == expectedDeviceType { - actualResult = result - break - } - } - - require.NotNil(t, actualResult, "应该找到设备类型 %s 的结果", expectedDeviceType) - - // 验证各个统计值 - assert.Equal(t, expectedResult["total_count"], actualResult["total_count"], - "设备类型 %s 的total_count应该匹配", expectedDeviceType) - assert.Equal(t, expectedResult["null_count"], actualResult["null_count"], - "设备类型 %s 的null_count应该匹配", expectedDeviceType) - assert.Equal(t, expectedResult["non_null_count"], actualResult["non_null_count"], - "设备类型 %s 的non_null_count应该匹配", expectedDeviceType) - } - } else { - // 验证普通查询的结果 - expectedDeviceIds := make([]string, len(tc.expected)) - for i, exp := range tc.expected { - expectedDeviceIds[i] = exp["deviceId"].(string) - } - - actualDeviceIds := make([]string, len(results)) - for i, result := range results { - actualDeviceIds[i] = result["deviceId"].(string) - } - - // 验证每个期望的设备ID都在结果中 - for _, expectedId := range expectedDeviceIds { - assert.Contains(t, actualDeviceIds, expectedId, "结果应该包含设备ID %s", expectedId) - } - - // 验证每个结果的字段值 - for _, result := range results { - deviceId := result["deviceId"].(string) - // 找到对应的期望结果 - var expectedResult map[string]interface{} - for _, exp := range tc.expected { - if exp["deviceId"].(string) == deviceId { - expectedResult = exp - break - } - } - - if expectedResult != nil { - for key, expectedValue := range expectedResult { - if key != "deviceId" { // deviceId已经验证过了 - actualValue := result[key] - assert.Equal(t, expectedValue, actualValue, - "设备 %s 的字段 %s 值应该匹配: 期望 %v, 实际 %v", deviceId, key, expectedValue, actualValue) - } - } - } - } - } - }) - } -} - -// TestNullComparisons 测试 = nil、!= nil、= null、!= null 等语法 -func TestNullComparisons(t *testing.T) { - testCases := []struct { - name string - sql string - testData []map[string]interface{} - expected []map[string]interface{} - }{ - { - name: "fieldName = nil 测试", - sql: "SELECT deviceId, value FROM stream WHERE value = nil", - testData: []map[string]interface{}{ - {"deviceId": "sensor1", "value": 25.5}, - {"deviceId": "sensor2", "value": nil}, - {"deviceId": "sensor3", "value": 30.0}, - {"deviceId": "sensor4", "value": nil}, - }, - expected: []map[string]interface{}{ - {"deviceId": "sensor2", "value": nil}, - {"deviceId": "sensor4", "value": nil}, - }, - }, - { - name: "fieldName != nil 测试", - sql: "SELECT deviceId, value FROM stream WHERE value != nil", - testData: []map[string]interface{}{ - {"deviceId": "sensor1", "value": 25.5}, - {"deviceId": "sensor2", "value": nil}, - {"deviceId": "sensor3", "value": 30.0}, - {"deviceId": "sensor4", "value": nil}, - }, - expected: []map[string]interface{}{ - {"deviceId": "sensor1", "value": 25.5}, - {"deviceId": "sensor3", "value": 30.0}, - }, - }, - { - name: "fieldName = null 测试", - sql: "SELECT deviceId, value FROM stream WHERE value = null", - testData: []map[string]interface{}{ - {"deviceId": "sensor1", "value": 25.5}, - {"deviceId": "sensor2", "value": nil}, - {"deviceId": "sensor3", "value": 30.0}, - {"deviceId": "sensor4", "value": nil}, - }, - expected: []map[string]interface{}{ - {"deviceId": "sensor2", "value": nil}, - {"deviceId": "sensor4", "value": nil}, - }, - }, - { - name: "fieldName != null 测试", - sql: "SELECT deviceId, value FROM stream WHERE value != null", - testData: []map[string]interface{}{ - {"deviceId": "sensor1", "value": 25.5}, - {"deviceId": "sensor2", "value": nil}, - {"deviceId": "sensor3", "value": 30.0}, - {"deviceId": "sensor4", "value": nil}, - }, - expected: []map[string]interface{}{ - {"deviceId": "sensor1", "value": 25.5}, - {"deviceId": "sensor3", "value": 30.0}, - }, - }, - { - name: "嵌套字段 = nil 测试", - sql: "SELECT deviceId, device.location FROM stream WHERE device.location = nil", - testData: []map[string]interface{}{ - { - "deviceId": "sensor1", - "device": map[string]interface{}{ - "location": "warehouse-A", - }, - }, - { - "deviceId": "sensor2", - "device": map[string]interface{}{ - "location": nil, - }, - }, - { - "deviceId": "sensor3", - "device": map[string]interface{}{}, - }, - }, - expected: []map[string]interface{}{ - {"deviceId": "sensor2", "device.location": nil}, - {"deviceId": "sensor3", "device.location": nil}, // 字段不存在也被认为是null - }, - }, - { - name: "嵌套字段 != nil 测试", - sql: "SELECT deviceId, device.location FROM stream WHERE device.location != nil", - testData: []map[string]interface{}{ - { - "deviceId": "sensor1", - "device": map[string]interface{}{ - "location": "warehouse-A", - }, - }, - { - "deviceId": "sensor2", - "device": map[string]interface{}{ - "location": nil, - }, - }, - { - "deviceId": "sensor3", - "device": map[string]interface{}{}, - }, - }, - expected: []map[string]interface{}{ - {"deviceId": "sensor1", "device.location": "warehouse-A"}, - }, - }, - { - name: "组合条件 - != nil AND 其他条件", - sql: "SELECT deviceId, value, status FROM stream WHERE value != nil AND value > 20", - testData: []map[string]interface{}{ - {"deviceId": "sensor1", "value": 25.5, "status": "active"}, - {"deviceId": "sensor2", "value": nil, "status": "active"}, - {"deviceId": "sensor3", "value": 15.0, "status": "inactive"}, - {"deviceId": "sensor4", "value": 30.0, "status": "active"}, - }, - expected: []map[string]interface{}{ - {"deviceId": "sensor1", "value": 25.5, "status": "active"}, - {"deviceId": "sensor4", "value": 30.0, "status": "active"}, - }, - }, - { - name: "组合条件 - = nil OR 其他条件", - sql: "SELECT deviceId, value, status FROM stream WHERE value = nil OR status = 'error'", - testData: []map[string]interface{}{ - {"deviceId": "sensor1", "value": 25.5, "status": "active"}, - {"deviceId": "sensor2", "value": nil, "status": "active"}, - {"deviceId": "sensor3", "value": 30.0, "status": "error"}, - {"deviceId": "sensor4", "value": nil, "status": "inactive"}, - }, - expected: []map[string]interface{}{ - {"deviceId": "sensor2", "value": nil, "status": "active"}, - {"deviceId": "sensor3", "value": 30.0, "status": "error"}, - {"deviceId": "sensor4", "value": nil, "status": "inactive"}, - }, - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - // 创建StreamSQL实例 - ssql := New() - defer ssql.Stop() - - // 执行SQL - err := ssql.Execute(tc.sql) - require.NoError(t, err) - - // 收集结果 - var results []map[string]interface{} - resultChan := make(chan interface{}, 10) - - ssql.Stream().AddSink(func(result interface{}) { - resultChan <- result - }) - - // 添加测试数据 - for _, data := range tc.testData { - ssql.Stream().AddData(data) - } - - // 使用超时方式安全收集结果 - timeout := time.After(500 * time.Millisecond) - - collecting: - for { - select { - case result := <-resultChan: - if resultSlice, ok := result.([]map[string]interface{}); ok { - results = append(results, resultSlice...) - } - case <-timeout: - break collecting - } - } - - // 验证结果数量 - assert.Len(t, results, len(tc.expected), "结果数量应该匹配") - - // 验证结果内容(不依赖顺序) - expectedDeviceIds := make([]string, len(tc.expected)) - for i, exp := range tc.expected { - expectedDeviceIds[i] = exp["deviceId"].(string) - } - - actualDeviceIds := make([]string, len(results)) - for i, result := range results { - actualDeviceIds[i] = result["deviceId"].(string) - } - - // 验证每个期望的设备ID都在结果中 - for _, expectedId := range expectedDeviceIds { - assert.Contains(t, actualDeviceIds, expectedId, "结果应该包含设备ID %s", expectedId) - } - - // 验证每个结果的字段值 - for _, result := range results { - deviceId := result["deviceId"].(string) - // 找到对应的期望结果 - var expectedResult map[string]interface{} - for _, exp := range tc.expected { - if exp["deviceId"].(string) == deviceId { - expectedResult = exp - break - } - } - - if expectedResult != nil { - for key, expectedValue := range expectedResult { - actualValue := result[key] - assert.Equal(t, expectedValue, actualValue, - "设备 %s 的字段 %s 值应该匹配: 期望 %v, 实际 %v", deviceId, key, expectedValue, actualValue) - } - } - } - }) - } -} - -// TestNullComparisonInAggregation 测试聚合查询中的 = nil 和 != nil -func TestNullComparisonInAggregation(t *testing.T) { - ssql := New() - defer ssql.Stop() - - // 聚合查询:统计非空值的数量 - sql := `SELECT deviceType, - COUNT(*) as total_count, - COUNT(value) as non_null_count - FROM stream - WHERE value != nil - GROUP BY deviceType, TumblingWindow('2s')` - - err := ssql.Execute(sql) - require.NoError(t, err) - - // 收集结果 - resultChan := make(chan interface{}, 10) - ssql.Stream().AddSink(func(result interface{}) { - resultChan <- result - }) - - // 添加测试数据 - testData := []map[string]interface{}{ - {"deviceType": "temperature", "value": 25.5}, - {"deviceType": "temperature", "value": nil}, - {"deviceType": "temperature", "value": 27.0}, - {"deviceType": "humidity", "value": 60.0}, - {"deviceType": "humidity", "value": nil}, - } - - for _, data := range testData { - ssql.Stream().AddData(data) - } - - // 等待窗口触发 - time.Sleep(3 * time.Second) - - // 验证结果 - select { - case result := <-resultChan: - resultSlice, ok := result.([]map[string]interface{}) - require.True(t, ok, "结果应该是[]map[string]interface{}类型") - - // 应该有temperature和humidity两种类型的结果 - assert.GreaterOrEqual(t, len(resultSlice), 1, "应该至少有一个聚合结果") - - for _, item := range resultSlice { - deviceType := item["deviceType"] - totalCount, _ := item["total_count"].(float64) - nonNullCount, _ := item["non_null_count"].(float64) - - if deviceType == "temperature" { - // temperature有2个非空值(25.5, 27.0) - assert.Equal(t, 2.0, totalCount, "temperature总数应该是2") - assert.Equal(t, 2.0, nonNullCount, "temperature非空数应该是2") - } else if deviceType == "humidity" { - // humidity有1个非空值(60.0) - assert.Equal(t, 1.0, totalCount, "humidity总数应该是1") - assert.Equal(t, 1.0, nonNullCount, "humidity非空数应该是1") - } - } - case <-time.After(5 * time.Second): - t.Fatal("测试超时,未收到聚合结果") - } -} - -// TestMixedNullComparisons 测试混合使用 IS NULL、= nil、= null、!= null 等语法 -func TestMixedNullComparisons(t *testing.T) { - ssql := New() - defer ssql.Stop() - - // 测试混合null比较语法 - sql := `SELECT deviceId, value, status, priority - FROM stream - WHERE (value IS NOT NULL AND value > 20) OR - (status = nil AND priority != null)` - - err := ssql.Execute(sql) - require.NoError(t, err) - - resultChan := make(chan interface{}, 10) - ssql.Stream().AddSink(func(result interface{}) { - resultChan <- result - }) - - // 添加测试数据 - testData := []map[string]interface{}{ - {"deviceId": "sensor1", "value": 25.0, "status": "active", "priority": "high"}, // 满足第一个条件 - {"deviceId": "sensor2", "value": 15.0, "status": "active", "priority": "low"}, // 不满足条件 - {"deviceId": "sensor3", "value": nil, "status": nil, "priority": "medium"}, // 满足第二个条件 - {"deviceId": "sensor4", "value": nil, "status": nil, "priority": nil}, // 不满足条件 - {"deviceId": "sensor5", "value": 30.0, "status": "inactive", "priority": nil}, // 满足第一个条件 - {"deviceId": "sensor6", "value": 10.0, "status": nil, "priority": "urgent"}, // 满足第二个条件 - } - - for _, data := range testData { - ssql.Stream().AddData(data) - } - - // 使用超时方式安全收集结果 - var results []map[string]interface{} - timeout := time.After(500 * time.Millisecond) - -collecting: - for { - select { - case result := <-resultChan: - if resultSlice, ok := result.([]map[string]interface{}); ok { - results = append(results, resultSlice...) - } - case <-timeout: - break collecting - } - } - - // 验证结果:应该有sensor1, sensor3, sensor5, sensor6 - assert.Len(t, results, 4, "应该有4个结果") - - expectedDeviceIds := []string{"sensor1", "sensor3", "sensor5", "sensor6"} - actualDeviceIds := make([]string, len(results)) - for i, result := range results { - actualDeviceIds[i] = result["deviceId"].(string) - } - - for _, expectedId := range expectedDeviceIds { - assert.Contains(t, actualDeviceIds, expectedId, "结果应该包含设备ID %s", expectedId) - } -} +package streamsql + +import ( + "fmt" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// TestIsNullOperatorInSQL 测试IS NULL和IS NOT NULL语法功能 +func TestIsNullOperatorInSQL(t *testing.T) { + testCases := []struct { + name string + sql string + testData []map[string]interface{} + expected []map[string]interface{} + }{ + { + name: "IS NULL测试", + sql: "SELECT deviceId, value FROM stream WHERE value IS NULL", + testData: []map[string]interface{}{ + {"deviceId": "sensor1", "value": 25.5}, + {"deviceId": "sensor2", "value": nil}, + {"deviceId": "sensor3", "value": 30.0}, + {"deviceId": "sensor4", "value": nil}, + }, + expected: []map[string]interface{}{ + {"deviceId": "sensor2", "value": nil}, + {"deviceId": "sensor4", "value": nil}, + }, + }, + { + name: "IS NOT NULL测试", + sql: "SELECT deviceId, value FROM stream WHERE value IS NOT NULL", + testData: []map[string]interface{}{ + {"deviceId": "sensor1", "value": 25.5}, + {"deviceId": "sensor2", "value": nil}, + {"deviceId": "sensor3", "value": 30.0}, + {"deviceId": "sensor4", "value": nil}, + }, + expected: []map[string]interface{}{ + {"deviceId": "sensor1", "value": 25.5}, + {"deviceId": "sensor3", "value": 30.0}, + }, + }, + { + name: "嵌套字段IS NULL测试", + sql: "SELECT deviceId, device.location FROM stream WHERE device.location IS NULL", + testData: []map[string]interface{}{ + { + "deviceId": "sensor1", + "device": map[string]interface{}{ + "location": "warehouse-A", + }, + }, + { + "deviceId": "sensor2", + "device": map[string]interface{}{ + "location": nil, + }, + }, + { + "deviceId": "sensor3", + "device": map[string]interface{}{}, + }, + }, + expected: []map[string]interface{}{ + {"deviceId": "sensor2", "device.location": nil}, + {"deviceId": "sensor3", "device.location": nil}, // 字段不存在也被认为是null + }, + }, + { + name: "组合条件 - IS NULL AND其他条件", + sql: "SELECT deviceId, value, status FROM stream WHERE value IS NULL AND status = 'active'", + testData: []map[string]interface{}{ + {"deviceId": "sensor1", "value": 25.5, "status": "active"}, + {"deviceId": "sensor2", "value": nil, "status": "active"}, + {"deviceId": "sensor3", "value": nil, "status": "inactive"}, + {"deviceId": "sensor4", "value": 30.0, "status": "active"}, + }, + expected: []map[string]interface{}{ + {"deviceId": "sensor2", "value": nil, "status": "active"}, + }, + }, + { + name: "组合条件 - IS NOT NULL OR其他条件", + sql: "SELECT deviceId, value, status FROM stream WHERE value IS NOT NULL OR status = 'error'", + testData: []map[string]interface{}{ + {"deviceId": "sensor1", "value": 25.5, "status": "active"}, + {"deviceId": "sensor2", "value": nil, "status": "active"}, + {"deviceId": "sensor3", "value": nil, "status": "error"}, + {"deviceId": "sensor4", "value": 30.0, "status": "inactive"}, + }, + expected: []map[string]interface{}{ + {"deviceId": "sensor1", "value": 25.5, "status": "active"}, + {"deviceId": "sensor3", "value": nil, "status": "error"}, + {"deviceId": "sensor4", "value": 30.0, "status": "inactive"}, + }, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + // 创建StreamSQL实例 + ssql := New() + defer ssql.Stop() + + // 执行SQL + err := ssql.Execute(tc.sql) + require.NoError(t, err) + + // 收集结果 + var results []map[string]interface{} + resultChan := make(chan interface{}, 10) + + ssql.Stream().AddSink(func(result interface{}) { + resultChan <- result + }) + + // 使用一个done channel来同步 + done := make(chan bool, 1) + + // 添加测试数据 + for _, data := range tc.testData { + ssql.Stream().Emit(data) + } + + // 在另一个goroutine中收集结果 + go func() { + defer func() { done <- true }() + // 等待一段时间收集结果 + timeout := time.After(300 * time.Millisecond) + for { + select { + case result := <-resultChan: + if resultSlice, ok := result.([]map[string]interface{}); ok { + results = append(results, resultSlice...) + } + case <-timeout: + return + } + } + }() + + // 等待收集完成 + <-done + + // 验证结果数量 + assert.Len(t, results, len(tc.expected), "结果数量应该匹配") + + // 验证结果内容(不依赖顺序) + expectedDeviceIds := make([]string, len(tc.expected)) + for i, exp := range tc.expected { + expectedDeviceIds[i] = exp["deviceId"].(string) + } + + actualDeviceIds := make([]string, len(results)) + for i, result := range results { + actualDeviceIds[i] = result["deviceId"].(string) + } + + // 验证每个期望的设备ID都在结果中 + for _, expectedId := range expectedDeviceIds { + assert.Contains(t, actualDeviceIds, expectedId, "结果应该包含设备ID %s", expectedId) + } + + // 验证每个结果的字段值 + for _, result := range results { + deviceId := result["deviceId"].(string) + // 找到对应的期望结果 + var expectedResult map[string]interface{} + for _, exp := range tc.expected { + if exp["deviceId"].(string) == deviceId { + expectedResult = exp + break + } + } + + if expectedResult != nil { + for key, expectedValue := range expectedResult { + actualValue := result[key] + assert.Equal(t, expectedValue, actualValue, + "设备 %s 的字段 %s 值应该匹配: 期望 %v, 实际 %v", deviceId, key, expectedValue, actualValue) + } + } + } + }) + } +} + +// TestIsNullInAggregation 测试聚合查询中的IS NULL +func TestIsNullInAggregation(t *testing.T) { + ssql := New() + defer ssql.Stop() + + // 聚合查询:统计非空值的数量 + sql := `SELECT deviceType, + COUNT(*) as total_count, + COUNT(value) as non_null_count + FROM stream + WHERE value IS NOT NULL + GROUP BY deviceType, TumblingWindow('2s')` + + err := ssql.Execute(sql) + require.NoError(t, err) + + // 收集结果 + resultChan := make(chan interface{}, 10) + ssql.Stream().AddSink(func(result interface{}) { + resultChan <- result + }) + + // 添加测试数据 + testData := []map[string]interface{}{ + {"deviceType": "temperature", "value": 25.5}, + {"deviceType": "temperature", "value": nil}, + {"deviceType": "temperature", "value": 27.0}, + {"deviceType": "humidity", "value": 60.0}, + {"deviceType": "humidity", "value": nil}, + } + + for _, data := range testData { + ssql.Stream().Emit(data) + } + + // 等待窗口触发 + time.Sleep(3 * time.Second) + + // 验证结果 + select { + case result := <-resultChan: + resultSlice, ok := result.([]map[string]interface{}) + require.True(t, ok, "结果应该是[]map[string]interface{}类型") + + // 应该有temperature和humidity两种类型的结果 + assert.GreaterOrEqual(t, len(resultSlice), 1, "应该至少有一个聚合结果") + + for _, item := range resultSlice { + deviceType := item["deviceType"] + totalCount, _ := item["total_count"].(float64) + nonNullCount, _ := item["non_null_count"].(float64) + + if deviceType == "temperature" { + // temperature有2个非空值(25.5, 27.0) + assert.Equal(t, 2.0, totalCount, "temperature总数应该是2") + assert.Equal(t, 2.0, nonNullCount, "temperature非空数应该是2") + } else if deviceType == "humidity" { + // humidity有1个非空值(60.0) + assert.Equal(t, 1.0, totalCount, "humidity总数应该是1") + assert.Equal(t, 1.0, nonNullCount, "humidity非空数应该是1") + } + } + case <-time.After(5 * time.Second): + t.Fatal("测试超时,未收到聚合结果") + } +} + +// TestIsNullInHaving 测试HAVING子句中真正的IS NULL功能 +func TestIsNullInHaving(t *testing.T) { + ssql := New() + defer ssql.Stop() + + // 测试HAVING子句中的IS NULL:只返回平均值为NULL的设备类型 + sql := `SELECT deviceType, + COUNT(*) as total_count, + AVG(value) as avg_value + FROM stream + GROUP BY deviceType, TumblingWindow('2s') + HAVING avg_value IS NULL` + + err := ssql.Execute(sql) + require.NoError(t, err) + + resultChan := make(chan interface{}, 10) + ssql.Stream().AddSink(func(result interface{}) { + resultChan <- result + }) + + // 添加测试数据:只给pressure设备类型添加null值,这样它的平均值会是null + testData := []map[string]interface{}{ + {"deviceType": "temperature", "value": 25.0}, + {"deviceType": "temperature", "value": 27.0}, // temperature有值,平均值不为null + {"deviceType": "humidity", "value": 60.0}, // humidity有值,平均值不为null + {"deviceType": "pressure", "value": nil}, // pressure只有null值 + {"deviceType": "pressure", "value": nil}, // pressure再次null值,平均值会是null + } + + for _, data := range testData { + ssql.Stream().Emit(data) + } + + // 等待窗口触发 + time.Sleep(3 * time.Second) + + // 验证结果 + select { + case result := <-resultChan: + resultSlice, ok := result.([]map[string]interface{}) + require.True(t, ok, "结果应该是[]map[string]interface{}类型") + + // 应该只有pressure类型的结果(平均值为null) + assert.Len(t, resultSlice, 1, "应该只有一个结果") + + if len(resultSlice) > 0 { + item := resultSlice[0] + assert.Equal(t, "pressure", item["deviceType"], "应该是pressure类型") + + // 验证avg_value确实为null + avgValue := item["avg_value"] + assert.Nil(t, avgValue, "pressure的平均值应该是null") + + // 验证total_count + totalCount, ok := item["total_count"].(float64) + assert.True(t, ok, "total_count应该是float64类型") + assert.Equal(t, 2.0, totalCount, "pressure应该有2条记录") + } + + case <-time.After(5 * time.Second): + t.Fatal("测试超时,未收到聚合结果") + } +} + +// TestIsNullInHavingWithIsNotNull 测试HAVING子句中的IS NOT NULL功能 +func TestIsNullInHavingWithIsNotNull(t *testing.T) { + ssql := New() + defer ssql.Stop() + + // 测试HAVING子句中的IS NOT NULL:只返回平均值不为NULL的设备类型 + sql := `SELECT deviceType, + COUNT(*) as total_count, + AVG(value) as avg_value + FROM stream + GROUP BY deviceType, TumblingWindow('2s') + HAVING avg_value IS NOT NULL` + + err := ssql.Execute(sql) + require.NoError(t, err) + + resultChan := make(chan interface{}, 10) + ssql.Stream().AddSink(func(result interface{}) { + resultChan <- result + }) + + // 添加测试数据 + testData := []map[string]interface{}{ + {"deviceType": "temperature", "value": 25.0}, + {"deviceType": "temperature", "value": 27.0}, // temperature有值,平均值不为null + {"deviceType": "humidity", "value": 60.0}, // humidity有值,平均值不为null + {"deviceType": "pressure", "value": nil}, // pressure只有null值,平均值会是null + {"deviceType": "pressure", "value": nil}, + } + + for _, data := range testData { + ssql.Stream().Emit(data) + } + + // 等待窗口触发 + time.Sleep(3 * time.Second) + + // 验证结果 + select { + case result := <-resultChan: + resultSlice, ok := result.([]map[string]interface{}) + require.True(t, ok, "结果应该是[]map[string]interface{}类型") + + // 应该有temperature和humidity两种类型的结果(平均值不为null) + assert.Len(t, resultSlice, 2, "应该有两个结果") + + foundTypes := make([]string, 0) + for _, item := range resultSlice { + deviceType, ok := item["deviceType"].(string) + require.True(t, ok, "deviceType应该是string类型") + + // 验证avg_value不为null + avgValue := item["avg_value"] + assert.NotNil(t, avgValue, fmt.Sprintf("%s的平均值应该不为null", deviceType)) + + foundTypes = append(foundTypes, deviceType) + } + + // 验证包含temperature和humidity,不包含pressure + assert.Contains(t, foundTypes, "temperature", "结果应该包含temperature") + assert.Contains(t, foundTypes, "humidity", "结果应该包含humidity") + assert.NotContains(t, foundTypes, "pressure", "结果不应该包含pressure") + + case <-time.After(5 * time.Second): + t.Fatal("测试超时,未收到聚合结果") + } +} + +// TestIsNullWithOtherOperators 测试IS NULL与其他操作符的组合 +func TestIsNullWithOtherOperators(t *testing.T) { + ssql := New() + defer ssql.Stop() + + // 测试复杂的WHERE条件 + sql := `SELECT deviceId, value, status, location + FROM stream + WHERE (value IS NOT NULL AND value > 20) OR + (status IS NULL AND location LIKE 'warehouse%')` + + err := ssql.Execute(sql) + require.NoError(t, err) + + resultChan := make(chan interface{}, 10) + ssql.Stream().AddSink(func(result interface{}) { + resultChan <- result + }) + + // 添加测试数据 + testData := []map[string]interface{}{ + {"deviceId": "sensor1", "value": 25.0, "status": "active", "location": "warehouse-A"}, // 满足第一个条件 + {"deviceId": "sensor2", "value": 15.0, "status": "active", "location": "warehouse-B"}, // 不满足条件 + {"deviceId": "sensor3", "value": nil, "status": nil, "location": "warehouse-C"}, // 满足第二个条件 + {"deviceId": "sensor4", "value": nil, "status": "inactive", "location": "warehouse-D"}, // 不满足条件 + {"deviceId": "sensor5", "value": 30.0, "status": nil, "location": "office-A"}, // 满足第一个条件 + } + + for _, data := range testData { + ssql.Stream().Emit(data) + } + + // 使用超时方式安全收集结果 + var results []map[string]interface{} + timeout := time.After(500 * time.Millisecond) + +collecting: + for { + select { + case result := <-resultChan: + if resultSlice, ok := result.([]map[string]interface{}); ok { + results = append(results, resultSlice...) + } + case <-timeout: + break collecting + } + } + + // 验证结果:应该有sensor1, sensor3, sensor5 + assert.Len(t, results, 3, "应该有3个结果") + + expectedDeviceIds := []string{"sensor1", "sensor3", "sensor5"} + actualDeviceIds := make([]string, len(results)) + for i, result := range results { + actualDeviceIds[i] = result["deviceId"].(string) + } + + for _, expectedId := range expectedDeviceIds { + assert.Contains(t, actualDeviceIds, expectedId, "结果应该包含设备ID %s", expectedId) + } +} + +// TestCaseWhenWithIsNull 测试CASE WHEN表达式中使用IS NULL和IS NOT NULL +func TestCaseWhenWithIsNull(t *testing.T) { + testCases := []struct { + name string + sql string + testData []map[string]interface{} + expected []map[string]interface{} + }{ + { + name: "CASE WHEN IS NULL基本测试", + sql: `SELECT deviceId, + CASE WHEN status IS NULL THEN 0 + WHEN status IS NOT NULL THEN 1 + ELSE 2 END as status_flag + FROM stream`, + testData: []map[string]interface{}{ + {"deviceId": "sensor1", "status": "active"}, + {"deviceId": "sensor2", "status": nil}, + {"deviceId": "sensor3", "status": "inactive"}, + {"deviceId": "sensor4"}, // 没有status字段 + }, + expected: []map[string]interface{}{ + {"deviceId": "sensor1", "status_flag": 1.0}, + {"deviceId": "sensor2", "status_flag": 0.0}, + {"deviceId": "sensor3", "status_flag": 1.0}, + {"deviceId": "sensor4", "status_flag": 0.0}, + }, + }, + { + name: "CASE WHEN IS NOT NULL复杂条件测试", + sql: `SELECT deviceId, + CASE WHEN temperature IS NOT NULL AND temperature > 25 THEN 2 + WHEN temperature IS NOT NULL AND temperature <= 25 THEN 1 + WHEN temperature IS NULL THEN 0 + ELSE 3 END as temp_level + FROM stream`, + testData: []map[string]interface{}{ + {"deviceId": "sensor1", "temperature": 30.0}, + {"deviceId": "sensor2", "temperature": 20.0}, + {"deviceId": "sensor3", "temperature": nil}, + {"deviceId": "sensor4"}, // 没有temperature字段 + }, + expected: []map[string]interface{}{ + {"deviceId": "sensor1", "temp_level": 2.0}, + {"deviceId": "sensor2", "temp_level": 1.0}, + {"deviceId": "sensor3", "temp_level": 0.0}, + {"deviceId": "sensor4", "temp_level": 0.0}, + }, + }, + { + name: "多个CASE WHEN IS NULL条件测试", + sql: `SELECT deviceId, + CASE WHEN status IS NULL AND temperature IS NULL THEN 0 + WHEN status IS NULL AND temperature IS NOT NULL THEN 1 + WHEN status IS NOT NULL AND temperature IS NULL THEN 2 + WHEN status IS NOT NULL AND temperature IS NOT NULL THEN 3 + ELSE 4 END as combined_flag + FROM stream`, + testData: []map[string]interface{}{ + {"deviceId": "sensor1", "status": "active", "temperature": 25.0}, + {"deviceId": "sensor2", "status": "active", "temperature": nil}, + {"deviceId": "sensor3", "status": nil, "temperature": 30.0}, + {"deviceId": "sensor4", "status": nil, "temperature": nil}, + {"deviceId": "sensor5"}, // 两个字段都不存在 + }, + expected: []map[string]interface{}{ + {"deviceId": "sensor1", "combined_flag": 3.0}, + {"deviceId": "sensor2", "combined_flag": 2.0}, + {"deviceId": "sensor3", "combined_flag": 1.0}, + {"deviceId": "sensor4", "combined_flag": 0.0}, + {"deviceId": "sensor5", "combined_flag": 0.0}, + }, + }, + { + name: "CASE WHEN IS NULL与聚合函数结合测试", + sql: `SELECT deviceType, + COUNT(*) as total_count, + SUM(CASE WHEN value IS NULL THEN 1 ELSE 0 END) as null_count, + SUM(CASE WHEN value IS NOT NULL THEN 1 ELSE 0 END) as non_null_count + FROM stream + GROUP BY deviceType, TumblingWindow('2s')`, + testData: []map[string]interface{}{ + {"deviceType": "temperature", "value": 25.0}, + {"deviceType": "temperature", "value": nil}, + {"deviceType": "temperature", "value": 27.0}, + {"deviceType": "humidity", "value": 60.0}, + {"deviceType": "humidity", "value": nil}, + {"deviceType": "humidity", "value": nil}, + }, + expected: []map[string]interface{}{ + { + "deviceType": "temperature", + "total_count": 3.0, + "null_count": 1.0, + "non_null_count": 2.0, + }, + { + "deviceType": "humidity", + "total_count": 3.0, + "null_count": 2.0, + "non_null_count": 1.0, + }, + }, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + // 创建StreamSQL实例 + ssql := New() + defer ssql.Stop() + + // 执行SQL + err := ssql.Execute(tc.sql) + require.NoError(t, err) + + // 收集结果 + var results []map[string]interface{} + resultChan := make(chan interface{}, 10) + + ssql.Stream().AddSink(func(result interface{}) { + resultChan <- result + }) + + // 添加测试数据 + for _, data := range tc.testData { + ssql.Stream().Emit(data) + } + + // 使用超时方式安全收集结果 + var timeout time.Duration + if tc.name == "CASE WHEN IS NULL与聚合函数结合测试" { + timeout = 4 * time.Second // 聚合查询需要更长时间 + } else { + timeout = 500 * time.Millisecond + } + + timeoutChan := time.After(timeout) + + collecting: + for { + select { + case result := <-resultChan: + if resultSlice, ok := result.([]map[string]interface{}); ok { + results = append(results, resultSlice...) + } + case <-timeoutChan: + break collecting + } + } + + // 验证结果数量 + assert.Len(t, results, len(tc.expected), "结果数量应该匹配") + + // 对于聚合查询,验证逻辑略有不同 + if tc.name == "CASE WHEN IS NULL与聚合函数结合测试" { + // 验证每个deviceType的结果 + for _, expectedResult := range tc.expected { + expectedDeviceType := expectedResult["deviceType"].(string) + + // 在结果中找到对应的deviceType + var actualResult map[string]interface{} + for _, result := range results { + if result["deviceType"].(string) == expectedDeviceType { + actualResult = result + break + } + } + + require.NotNil(t, actualResult, "应该找到设备类型 %s 的结果", expectedDeviceType) + + // 验证各个统计值 + assert.Equal(t, expectedResult["total_count"], actualResult["total_count"], + "设备类型 %s 的total_count应该匹配", expectedDeviceType) + assert.Equal(t, expectedResult["null_count"], actualResult["null_count"], + "设备类型 %s 的null_count应该匹配", expectedDeviceType) + assert.Equal(t, expectedResult["non_null_count"], actualResult["non_null_count"], + "设备类型 %s 的non_null_count应该匹配", expectedDeviceType) + } + } else { + // 验证普通查询的结果 + expectedDeviceIds := make([]string, len(tc.expected)) + for i, exp := range tc.expected { + expectedDeviceIds[i] = exp["deviceId"].(string) + } + + actualDeviceIds := make([]string, len(results)) + for i, result := range results { + actualDeviceIds[i] = result["deviceId"].(string) + } + + // 验证每个期望的设备ID都在结果中 + for _, expectedId := range expectedDeviceIds { + assert.Contains(t, actualDeviceIds, expectedId, "结果应该包含设备ID %s", expectedId) + } + + // 验证每个结果的字段值 + for _, result := range results { + deviceId := result["deviceId"].(string) + // 找到对应的期望结果 + var expectedResult map[string]interface{} + for _, exp := range tc.expected { + if exp["deviceId"].(string) == deviceId { + expectedResult = exp + break + } + } + + if expectedResult != nil { + for key, expectedValue := range expectedResult { + if key != "deviceId" { // deviceId已经验证过了 + actualValue := result[key] + assert.Equal(t, expectedValue, actualValue, + "设备 %s 的字段 %s 值应该匹配: 期望 %v, 实际 %v", deviceId, key, expectedValue, actualValue) + } + } + } + } + } + }) + } +} + +// TestNullComparisons 测试 = nil、!= nil、= null、!= null 等语法 +func TestNullComparisons(t *testing.T) { + testCases := []struct { + name string + sql string + testData []map[string]interface{} + expected []map[string]interface{} + }{ + { + name: "fieldName = nil 测试", + sql: "SELECT deviceId, value FROM stream WHERE value = nil", + testData: []map[string]interface{}{ + {"deviceId": "sensor1", "value": 25.5}, + {"deviceId": "sensor2", "value": nil}, + {"deviceId": "sensor3", "value": 30.0}, + {"deviceId": "sensor4", "value": nil}, + }, + expected: []map[string]interface{}{ + {"deviceId": "sensor2", "value": nil}, + {"deviceId": "sensor4", "value": nil}, + }, + }, + { + name: "fieldName != nil 测试", + sql: "SELECT deviceId, value FROM stream WHERE value != nil", + testData: []map[string]interface{}{ + {"deviceId": "sensor1", "value": 25.5}, + {"deviceId": "sensor2", "value": nil}, + {"deviceId": "sensor3", "value": 30.0}, + {"deviceId": "sensor4", "value": nil}, + }, + expected: []map[string]interface{}{ + {"deviceId": "sensor1", "value": 25.5}, + {"deviceId": "sensor3", "value": 30.0}, + }, + }, + { + name: "fieldName = null 测试", + sql: "SELECT deviceId, value FROM stream WHERE value = null", + testData: []map[string]interface{}{ + {"deviceId": "sensor1", "value": 25.5}, + {"deviceId": "sensor2", "value": nil}, + {"deviceId": "sensor3", "value": 30.0}, + {"deviceId": "sensor4", "value": nil}, + }, + expected: []map[string]interface{}{ + {"deviceId": "sensor2", "value": nil}, + {"deviceId": "sensor4", "value": nil}, + }, + }, + { + name: "fieldName != null 测试", + sql: "SELECT deviceId, value FROM stream WHERE value != null", + testData: []map[string]interface{}{ + {"deviceId": "sensor1", "value": 25.5}, + {"deviceId": "sensor2", "value": nil}, + {"deviceId": "sensor3", "value": 30.0}, + {"deviceId": "sensor4", "value": nil}, + }, + expected: []map[string]interface{}{ + {"deviceId": "sensor1", "value": 25.5}, + {"deviceId": "sensor3", "value": 30.0}, + }, + }, + { + name: "嵌套字段 = nil 测试", + sql: "SELECT deviceId, device.location FROM stream WHERE device.location = nil", + testData: []map[string]interface{}{ + { + "deviceId": "sensor1", + "device": map[string]interface{}{ + "location": "warehouse-A", + }, + }, + { + "deviceId": "sensor2", + "device": map[string]interface{}{ + "location": nil, + }, + }, + { + "deviceId": "sensor3", + "device": map[string]interface{}{}, + }, + }, + expected: []map[string]interface{}{ + {"deviceId": "sensor2", "device.location": nil}, + {"deviceId": "sensor3", "device.location": nil}, // 字段不存在也被认为是null + }, + }, + { + name: "嵌套字段 != nil 测试", + sql: "SELECT deviceId, device.location FROM stream WHERE device.location != nil", + testData: []map[string]interface{}{ + { + "deviceId": "sensor1", + "device": map[string]interface{}{ + "location": "warehouse-A", + }, + }, + { + "deviceId": "sensor2", + "device": map[string]interface{}{ + "location": nil, + }, + }, + { + "deviceId": "sensor3", + "device": map[string]interface{}{}, + }, + }, + expected: []map[string]interface{}{ + {"deviceId": "sensor1", "device.location": "warehouse-A"}, + }, + }, + { + name: "组合条件 - != nil AND 其他条件", + sql: "SELECT deviceId, value, status FROM stream WHERE value != nil AND value > 20", + testData: []map[string]interface{}{ + {"deviceId": "sensor1", "value": 25.5, "status": "active"}, + {"deviceId": "sensor2", "value": nil, "status": "active"}, + {"deviceId": "sensor3", "value": 15.0, "status": "inactive"}, + {"deviceId": "sensor4", "value": 30.0, "status": "active"}, + }, + expected: []map[string]interface{}{ + {"deviceId": "sensor1", "value": 25.5, "status": "active"}, + {"deviceId": "sensor4", "value": 30.0, "status": "active"}, + }, + }, + { + name: "组合条件 - = nil OR 其他条件", + sql: "SELECT deviceId, value, status FROM stream WHERE value = nil OR status = 'error'", + testData: []map[string]interface{}{ + {"deviceId": "sensor1", "value": 25.5, "status": "active"}, + {"deviceId": "sensor2", "value": nil, "status": "active"}, + {"deviceId": "sensor3", "value": 30.0, "status": "error"}, + {"deviceId": "sensor4", "value": nil, "status": "inactive"}, + }, + expected: []map[string]interface{}{ + {"deviceId": "sensor2", "value": nil, "status": "active"}, + {"deviceId": "sensor3", "value": 30.0, "status": "error"}, + {"deviceId": "sensor4", "value": nil, "status": "inactive"}, + }, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + // 创建StreamSQL实例 + ssql := New() + defer ssql.Stop() + + // 执行SQL + err := ssql.Execute(tc.sql) + require.NoError(t, err) + + // 收集结果 + var results []map[string]interface{} + resultChan := make(chan interface{}, 10) + + ssql.Stream().AddSink(func(result interface{}) { + resultChan <- result + }) + + // 添加测试数据 + for _, data := range tc.testData { + ssql.Stream().Emit(data) + } + + // 使用超时方式安全收集结果 + timeout := time.After(500 * time.Millisecond) + + collecting: + for { + select { + case result := <-resultChan: + if resultSlice, ok := result.([]map[string]interface{}); ok { + results = append(results, resultSlice...) + } + case <-timeout: + break collecting + } + } + + // 验证结果数量 + assert.Len(t, results, len(tc.expected), "结果数量应该匹配") + + // 验证结果内容(不依赖顺序) + expectedDeviceIds := make([]string, len(tc.expected)) + for i, exp := range tc.expected { + expectedDeviceIds[i] = exp["deviceId"].(string) + } + + actualDeviceIds := make([]string, len(results)) + for i, result := range results { + actualDeviceIds[i] = result["deviceId"].(string) + } + + // 验证每个期望的设备ID都在结果中 + for _, expectedId := range expectedDeviceIds { + assert.Contains(t, actualDeviceIds, expectedId, "结果应该包含设备ID %s", expectedId) + } + + // 验证每个结果的字段值 + for _, result := range results { + deviceId := result["deviceId"].(string) + // 找到对应的期望结果 + var expectedResult map[string]interface{} + for _, exp := range tc.expected { + if exp["deviceId"].(string) == deviceId { + expectedResult = exp + break + } + } + + if expectedResult != nil { + for key, expectedValue := range expectedResult { + actualValue := result[key] + assert.Equal(t, expectedValue, actualValue, + "设备 %s 的字段 %s 值应该匹配: 期望 %v, 实际 %v", deviceId, key, expectedValue, actualValue) + } + } + } + }) + } +} + +// TestNullComparisonInAggregation 测试聚合查询中的 = nil 和 != nil +func TestNullComparisonInAggregation(t *testing.T) { + ssql := New() + defer ssql.Stop() + + // 聚合查询:统计非空值的数量 + sql := `SELECT deviceType, + COUNT(*) as total_count, + COUNT(value) as non_null_count + FROM stream + WHERE value != nil + GROUP BY deviceType, TumblingWindow('2s')` + + err := ssql.Execute(sql) + require.NoError(t, err) + + // 收集结果 + resultChan := make(chan interface{}, 10) + ssql.Stream().AddSink(func(result interface{}) { + resultChan <- result + }) + + // 添加测试数据 + testData := []map[string]interface{}{ + {"deviceType": "temperature", "value": 25.5}, + {"deviceType": "temperature", "value": nil}, + {"deviceType": "temperature", "value": 27.0}, + {"deviceType": "humidity", "value": 60.0}, + {"deviceType": "humidity", "value": nil}, + } + + for _, data := range testData { + ssql.Stream().Emit(data) + } + + // 等待窗口触发 + time.Sleep(3 * time.Second) + + // 验证结果 + select { + case result := <-resultChan: + resultSlice, ok := result.([]map[string]interface{}) + require.True(t, ok, "结果应该是[]map[string]interface{}类型") + + // 应该有temperature和humidity两种类型的结果 + assert.GreaterOrEqual(t, len(resultSlice), 1, "应该至少有一个聚合结果") + + for _, item := range resultSlice { + deviceType := item["deviceType"] + totalCount, _ := item["total_count"].(float64) + nonNullCount, _ := item["non_null_count"].(float64) + + if deviceType == "temperature" { + // temperature有2个非空值(25.5, 27.0) + assert.Equal(t, 2.0, totalCount, "temperature总数应该是2") + assert.Equal(t, 2.0, nonNullCount, "temperature非空数应该是2") + } else if deviceType == "humidity" { + // humidity有1个非空值(60.0) + assert.Equal(t, 1.0, totalCount, "humidity总数应该是1") + assert.Equal(t, 1.0, nonNullCount, "humidity非空数应该是1") + } + } + case <-time.After(5 * time.Second): + t.Fatal("测试超时,未收到聚合结果") + } +} + +// TestMixedNullComparisons 测试混合使用 IS NULL、= nil、= null、!= null 等语法 +func TestMixedNullComparisons(t *testing.T) { + ssql := New() + defer ssql.Stop() + + // 测试混合null比较语法 + sql := `SELECT deviceId, value, status, priority + FROM stream + WHERE (value IS NOT NULL AND value > 20) OR + (status = nil AND priority != null)` + + err := ssql.Execute(sql) + require.NoError(t, err) + + resultChan := make(chan interface{}, 10) + ssql.Stream().AddSink(func(result interface{}) { + resultChan <- result + }) + + // 添加测试数据 + testData := []map[string]interface{}{ + {"deviceId": "sensor1", "value": 25.0, "status": "active", "priority": "high"}, // 满足第一个条件 + {"deviceId": "sensor2", "value": 15.0, "status": "active", "priority": "low"}, // 不满足条件 + {"deviceId": "sensor3", "value": nil, "status": nil, "priority": "medium"}, // 满足第二个条件 + {"deviceId": "sensor4", "value": nil, "status": nil, "priority": nil}, // 不满足条件 + {"deviceId": "sensor5", "value": 30.0, "status": "inactive", "priority": nil}, // 满足第一个条件 + {"deviceId": "sensor6", "value": 10.0, "status": nil, "priority": "urgent"}, // 满足第二个条件 + } + + for _, data := range testData { + ssql.Stream().Emit(data) + } + + // 使用超时方式安全收集结果 + var results []map[string]interface{} + timeout := time.After(500 * time.Millisecond) + +collecting: + for { + select { + case result := <-resultChan: + if resultSlice, ok := result.([]map[string]interface{}); ok { + results = append(results, resultSlice...) + } + case <-timeout: + break collecting + } + } + + // 验证结果:应该有sensor1, sensor3, sensor5, sensor6 + assert.Len(t, results, 4, "应该有4个结果") + + expectedDeviceIds := []string{"sensor1", "sensor3", "sensor5", "sensor6"} + actualDeviceIds := make([]string, len(results)) + for i, result := range results { + actualDeviceIds[i] = result["deviceId"].(string) + } + + for _, expectedId := range expectedDeviceIds { + assert.Contains(t, actualDeviceIds, expectedId, "结果应该包含设备ID %s", expectedId) + } +} diff --git a/streamsql_like_test.go b/streamsql_like_test.go index 931a15e..a7c2701 100644 --- a/streamsql_like_test.go +++ b/streamsql_like_test.go @@ -1,551 +1,551 @@ -package streamsql - -import ( - "context" - "fmt" - "strings" - "testing" - "time" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -// TestLikeOperatorInSQL 测试LIKE语法功能 -func TestLikeOperatorInSQL(t *testing.T) { - streamsql := New() - defer streamsql.Stop() - - // 测试场景1:基本LIKE模式匹配 - 前缀匹配 - t.Run("前缀匹配(prefix%)", func(t *testing.T) { - // 测试使用LIKE进行前缀匹配 - var rsql = "SELECT deviceId, deviceType FROM stream WHERE deviceId LIKE 'sensor%'" - err := streamsql.Execute(rsql) - assert.Nil(t, err) - strm := streamsql.stream - - // 创建结果接收通道 - resultChan := make(chan interface{}, 10) - - // 添加结果回调 - strm.AddSink(func(result interface{}) { - resultChan <- result - }) - - // 添加测试数据 - testData := []interface{}{ - map[string]interface{}{"deviceId": "sensor001", "deviceType": "temperature"}, - map[string]interface{}{"deviceId": "device002", "deviceType": "humidity"}, - map[string]interface{}{"deviceId": "sensor003", "deviceType": "pressure"}, - map[string]interface{}{"deviceId": "pump004", "deviceType": "actuator"}, - } - - // 添加数据 - for _, data := range testData { - strm.AddData(data) - } - - // 等待并收集结果 - var results []interface{} - timeout := time.After(2 * time.Second) - done := false - - for !done && len(results) < 2 { - select { - case result := <-resultChan: - results = append(results, result) - case <-timeout: - done = true - } - } - - // 验证结果:应该只有sensor001和sensor003匹配 - assert.GreaterOrEqual(t, len(results), 1, "应该收到至少一个匹配结果") - - // 验证结果中只包含以"sensor"开头的设备 - for _, result := range results { - resultSlice, ok := result.([]map[string]interface{}) - require.True(t, ok, "结果应该是[]map[string]interface{}类型") - - for _, item := range resultSlice { - deviceId, _ := item["deviceId"].(string) - assert.True(t, strings.HasPrefix(deviceId, "sensor"), - fmt.Sprintf("设备ID %s 应该以'sensor'开头", deviceId)) - } - } - }) - - // 测试场景2:后缀匹配 - t.Run("后缀匹配(%suffix)", func(t *testing.T) { - streamsql := New() - defer streamsql.Stop() - - var rsql = "SELECT deviceId, status FROM stream WHERE status LIKE '%error'" - err := streamsql.Execute(rsql) - assert.Nil(t, err) - strm := streamsql.stream - - resultChan := make(chan interface{}, 10) - strm.AddSink(func(result interface{}) { - resultChan <- result - }) - - testData := []interface{}{ - map[string]interface{}{"deviceId": "dev1", "status": "connection_error"}, - map[string]interface{}{"deviceId": "dev2", "status": "running"}, - map[string]interface{}{"deviceId": "dev3", "status": "timeout_error"}, - map[string]interface{}{"deviceId": "dev4", "status": "normal"}, - } - - for _, data := range testData { - strm.AddData(data) - } - - // 等待结果 - var results []interface{} - timeout := time.After(2 * time.Second) - done := false - - for !done && len(results) < 2 { - select { - case result := <-resultChan: - results = append(results, result) - case <-timeout: - done = true - } - } - - // 验证结果:应该只有以"error"结尾的状态 - assert.GreaterOrEqual(t, len(results), 1, "应该收到至少一个匹配结果") - - for _, result := range results { - resultSlice, ok := result.([]map[string]interface{}) - require.True(t, ok, "结果应该是[]map[string]interface{}类型") - - for _, item := range resultSlice { - status, _ := item["status"].(string) - assert.True(t, strings.HasSuffix(status, "error"), - fmt.Sprintf("状态 %s 应该以'error'结尾", status)) - } - } - }) - - // 测试场景3:包含匹配 - t.Run("包含匹配(%substring%)", func(t *testing.T) { - streamsql := New() - defer streamsql.Stop() - - var rsql = "SELECT deviceId, message FROM stream WHERE message LIKE '%alert%'" - err := streamsql.Execute(rsql) - assert.Nil(t, err) - strm := streamsql.stream - - resultChan := make(chan interface{}, 10) - strm.AddSink(func(result interface{}) { - resultChan <- result - }) - - testData := []interface{}{ - map[string]interface{}{"deviceId": "dev1", "message": "system alert: high temperature"}, - map[string]interface{}{"deviceId": "dev2", "message": "normal operation"}, - map[string]interface{}{"deviceId": "dev3", "message": "critical alert detected"}, - map[string]interface{}{"deviceId": "dev4", "message": "info: device startup"}, - } - - for _, data := range testData { - strm.AddData(data) - } - - // 等待结果 - var results []interface{} - timeout := time.After(2 * time.Second) - done := false - - for !done && len(results) < 2 { - select { - case result := <-resultChan: - results = append(results, result) - case <-timeout: - done = true - } - } - - // 验证结果:应该只有包含"alert"的消息 - assert.GreaterOrEqual(t, len(results), 1, "应该收到至少一个匹配结果") - - for _, result := range results { - resultSlice, ok := result.([]map[string]interface{}) - require.True(t, ok, "结果应该是[]map[string]interface{}类型") - - for _, item := range resultSlice { - message, _ := item["message"].(string) - assert.True(t, strings.Contains(message, "alert"), - fmt.Sprintf("消息 %s 应该包含'alert'", message)) - } - } - }) - - // 测试场景4:单字符通配符 - t.Run("单字符通配符(_)", func(t *testing.T) { - streamsql := New() - defer streamsql.Stop() - - var rsql = "SELECT deviceId, code FROM stream WHERE code LIKE 'E_0_'" - err := streamsql.Execute(rsql) - assert.Nil(t, err) - strm := streamsql.stream - - resultChan := make(chan interface{}, 10) - strm.AddSink(func(result interface{}) { - resultChan <- result - }) - - testData := []interface{}{ - map[string]interface{}{"deviceId": "dev1", "code": "E101"}, - map[string]interface{}{"deviceId": "dev2", "code": "E202"}, - map[string]interface{}{"deviceId": "dev3", "code": "E305"}, - map[string]interface{}{"deviceId": "dev4", "code": "F101"}, - } - - for _, data := range testData { - strm.AddData(data) - } - - // 等待结果 - var results []interface{} - timeout := time.After(2 * time.Second) - done := false - - for !done && len(results) < 2 { - select { - case result := <-resultChan: - results = append(results, result) - case <-timeout: - done = true - } - } - - // 验证结果:应该只有E_0_模式的代码(E101, E202不匹配E_0_,只有E305也不完全匹配) - // 实际上,根据模式E_0_,应该匹配如E101, E202等,让我们调整测试数据 - assert.GreaterOrEqual(t, len(results), 0, "根据通配符模式可能有匹配结果") - }) - - // 测试场景5:复杂模式 - t.Run("复杂LIKE模式", func(t *testing.T) { - streamsql := New() - defer streamsql.Stop() - - var rsql = "SELECT deviceId, filename FROM stream WHERE filename LIKE '%.log'" - err := streamsql.Execute(rsql) - assert.Nil(t, err) - strm := streamsql.stream - - resultChan := make(chan interface{}, 10) - strm.AddSink(func(result interface{}) { - resultChan <- result - }) - - testData := []interface{}{ - map[string]interface{}{"deviceId": "dev1", "filename": "system.log"}, - map[string]interface{}{"deviceId": "dev2", "filename": "config.txt"}, - map[string]interface{}{"deviceId": "dev3", "filename": "error.log"}, - map[string]interface{}{"deviceId": "dev4", "filename": "backup.bak"}, - } - - for _, data := range testData { - strm.AddData(data) - } - - // 等待结果 - var results []interface{} - timeout := time.After(2 * time.Second) - done := false - - for !done && len(results) < 2 { - select { - case result := <-resultChan: - results = append(results, result) - case <-timeout: - done = true - } - } - - // 验证结果:应该只有.log文件 - assert.GreaterOrEqual(t, len(results), 1, "应该收到至少一个匹配结果") - - for _, result := range results { - resultSlice, ok := result.([]map[string]interface{}) - require.True(t, ok, "结果应该是[]map[string]interface{}类型") - - for _, item := range resultSlice { - filename, _ := item["filename"].(string) - assert.True(t, strings.HasSuffix(filename, ".log"), - fmt.Sprintf("文件名 %s 应该以'.log'结尾", filename)) - } - } - }) - - // 测试场景6:在聚合查询中使用LIKE - t.Run("聚合查询中的LIKE", func(t *testing.T) { - streamsql := New() - defer streamsql.Stop() - - var rsql = "SELECT deviceType, count(*) as device_count FROM stream WHERE deviceId LIKE 'sensor%' GROUP BY deviceType" - err := streamsql.Execute(rsql) - assert.Nil(t, err) - strm := streamsql.stream - - resultChan := make(chan interface{}, 10) - strm.AddSink(func(result interface{}) { - resultChan <- result - }) - - testData := []interface{}{ - map[string]interface{}{"deviceId": "sensor001", "deviceType": "temperature"}, - map[string]interface{}{"deviceId": "sensor002", "deviceType": "temperature"}, - map[string]interface{}{"deviceId": "device003", "deviceType": "temperature"}, - map[string]interface{}{"deviceId": "sensor004", "deviceType": "humidity"}, - map[string]interface{}{"deviceId": "pump005", "deviceType": "actuator"}, - } - - for _, data := range testData { - strm.AddData(data) - } - - // 等待聚合 - time.Sleep(500 * time.Millisecond) - strm.Window.Trigger() - - // 等待结果 - ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) - defer cancel() - - var actual interface{} - select { - case actual = <-resultChan: - cancel() - case <-ctx.Done(): - t.Fatal("测试超时,未收到聚合结果") - } - - // 验证聚合结果 - resultSlice, ok := actual.([]map[string]interface{}) - require.True(t, ok, "结果应该是[]map[string]interface{}类型") - - // 应该有两种设备类型:temperature(2个sensor), humidity(1个sensor) - assert.GreaterOrEqual(t, len(resultSlice), 1, "应该有至少一种设备类型的聚合结果") - - for _, result := range resultSlice { - deviceType, _ := result["deviceType"].(string) - count, ok := result["device_count"].(float64) - assert.True(t, ok, "device_count应该是float64类型") - assert.Greater(t, count, 0.0, "设备数量应该大于0") - - // 验证设备类型 - assert.True(t, deviceType == "temperature" || deviceType == "humidity", - fmt.Sprintf("设备类型 %s 应该是temperature或humidity", deviceType)) - } - }) - - // 测试场景7:HAVING子句中的LIKE - t.Run("HAVING子句中的LIKE", func(t *testing.T) { - streamsql := New() - defer streamsql.Stop() - - var rsql = "SELECT deviceType, max(temperature) as max_temp FROM stream GROUP BY deviceType HAVING deviceType LIKE '%temp%'" - err := streamsql.Execute(rsql) - assert.Nil(t, err) - strm := streamsql.stream - - resultChan := make(chan interface{}, 10) - strm.AddSink(func(result interface{}) { - resultChan <- result - }) - - testData := []interface{}{ - map[string]interface{}{"deviceType": "temperature_sensor", "temperature": 25.0}, - map[string]interface{}{"deviceType": "temperature_sensor", "temperature": 30.0}, - map[string]interface{}{"deviceType": "humidity_sensor", "temperature": 22.0}, - map[string]interface{}{"deviceType": "pressure_gauge", "temperature": 20.0}, - } - - for _, data := range testData { - strm.AddData(data) - } - - // 等待聚合 - time.Sleep(500 * time.Millisecond) - strm.Window.Trigger() - - // 等待结果 - ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) - defer cancel() - - var actual interface{} - select { - case actual = <-resultChan: - cancel() - case <-ctx.Done(): - t.Fatal("测试超时,未收到HAVING+LIKE结果") - } - - // 验证HAVING + LIKE结果 - resultSlice, ok := actual.([]map[string]interface{}) - require.True(t, ok, "结果应该是[]map[string]interface{}类型") - - // 应该只有包含"temp"的设备类型 - for _, result := range resultSlice { - deviceType, _ := result["deviceType"].(string) - assert.True(t, strings.Contains(deviceType, "temp"), - fmt.Sprintf("设备类型 %s 应该包含'temp'", deviceType)) - - maxTemp, ok := result["max_temp"].(float64) - assert.True(t, ok, "max_temp应该是float64类型") - assert.Greater(t, maxTemp, 0.0, "最大温度应该大于0") - } - }) -} - -// TestLikeFunctionEquivalence 测试LIKE语法与现有字符串函数的等价性 -func TestLikeFunctionEquivalence(t *testing.T) { - // 简化测试,重点验证LIKE功能已经正常工作 - t.Run("LIKE语法工作正常验证", func(t *testing.T) { - streamsql := New() - defer streamsql.Stop() - - // 使用LIKE的查询 - var likeSQL = "SELECT deviceId FROM stream WHERE deviceId LIKE 'sensor%'" - err := streamsql.Execute(likeSQL) - assert.Nil(t, err) - - resultChan := make(chan interface{}, 10) - streamsql.stream.AddSink(func(result interface{}) { - resultChan <- result - }) - - // 测试数据 - testData := []interface{}{ - map[string]interface{}{"deviceId": "sensor001"}, - map[string]interface{}{"deviceId": "device002"}, - map[string]interface{}{"deviceId": "sensor003"}, - } - - // 添加数据 - for _, data := range testData { - streamsql.stream.AddData(data) - } - - // 收集结果 - timeout := time.After(2 * time.Second) - var results []interface{} - - for len(results) < 2 { - select { - case result := <-resultChan: - results = append(results, result) - case <-timeout: - break - } - } - - // 验证LIKE查询返回了预期的结果 - assert.Equal(t, 2, len(results), "LIKE查询应该返回2个匹配'sensor%'的结果") - t.Logf("LIKE查询成功返回%d个结果", len(results)) - - // 验证返回的结果确实是以'sensor'开头的 - for i, result := range results { - resultSlice, ok := result.([]map[string]interface{}) - assert.True(t, ok, fmt.Sprintf("结果%d应该是[]map[string]interface{}类型", i)) - if len(resultSlice) > 0 { - deviceId, exists := resultSlice[0]["deviceId"] - assert.True(t, exists, "结果应该包含deviceId字段") - deviceIdStr, ok := deviceId.(string) - assert.True(t, ok, "deviceId应该是字符串类型") - assert.True(t, strings.HasPrefix(deviceIdStr, "sensor"), - fmt.Sprintf("deviceId '%s' 应该以'sensor'开头", deviceIdStr)) - } - } - }) -} - -// TestLikePatternMatching 测试LIKE模式匹配算法的正确性 -func TestLikePatternMatching(t *testing.T) { - // 这些是单元测试,直接测试LIKE匹配函数 - tests := []struct { - text string - pattern string - expected bool - desc string - }{ - // 前缀匹配测试 - {"hello", "hello%", true, "精确前缀匹配"}, - {"hello world", "hello%", true, "前缀匹配"}, - {"hi there", "hello%", false, "前缀不匹配"}, - {"", "%", true, "空字符串匹配任意模式"}, - - // 后缀匹配测试 - {"test.log", "%.log", true, "后缀匹配"}, - {"test.txt", "%.log", false, "后缀不匹配"}, - - // 包含匹配测试 - {"hello world test", "%world%", true, "包含匹配"}, - {"hello test", "%world%", false, "不包含"}, - - // 单字符通配符测试 - {"abc", "a_c", true, "单字符通配符匹配"}, - {"aXc", "a_c", true, "单字符通配符匹配任意字符"}, - {"abbc", "a_c", false, "单字符通配符不匹配多个字符"}, - - // 复杂模式测试 - {"file123.log", "file___.log", true, "多个单字符通配符"}, - {"file12.log", "file___.log", false, "字符数不匹配"}, - {"prefix_test_suffix", "prefix%suffix", true, "前后缀组合"}, - - // 边界情况测试 - {"", "", true, "空模式匹配空字符串"}, - {"abc", "", false, "非空字符串不匹配空模式"}, - {"", "abc", false, "空字符串不匹配非空模式"}, - {"abc", "abc", true, "完全匹配"}, - } - - for _, test := range tests { - t.Run(test.desc, func(t *testing.T) { - // 直接使用内部函数进行测试 - // 注意:这里我们需要通过SQL查询来测试,因为匹配函数是内部的 - streamsql := New() - defer streamsql.Stop() - - // 构造SQL查询 - rsql := fmt.Sprintf("SELECT value FROM stream WHERE value LIKE '%s'", test.pattern) - err := streamsql.Execute(rsql) - assert.Nil(t, err) - - resultChan := make(chan interface{}, 10) - streamsql.stream.AddSink(func(result interface{}) { - resultChan <- result - }) - - // 添加测试数据 - testData := map[string]interface{}{"value": test.text} - streamsql.stream.AddData(testData) - - // 等待结果 - timeout := time.After(1 * time.Second) - var hasResult bool - - select { - case result := <-resultChan: - resultSlice, ok := result.([]map[string]interface{}) - hasResult = ok && len(resultSlice) > 0 - case <-timeout: - hasResult = false - } - - if test.expected { - assert.True(t, hasResult, fmt.Sprintf("模式'%s'应该匹配文本'%s'", test.pattern, test.text)) - } else { - assert.False(t, hasResult, fmt.Sprintf("模式'%s'不应该匹配文本'%s'", test.pattern, test.text)) - } - }) - } -} +package streamsql + +import ( + "context" + "fmt" + "strings" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// TestLikeOperatorInSQL 测试LIKE语法功能 +func TestLikeOperatorInSQL(t *testing.T) { + streamsql := New() + defer streamsql.Stop() + + // 测试场景1:基本LIKE模式匹配 - 前缀匹配 + t.Run("前缀匹配(prefix%)", func(t *testing.T) { + // 测试使用LIKE进行前缀匹配 + var rsql = "SELECT deviceId, deviceType FROM stream WHERE deviceId LIKE 'sensor%'" + err := streamsql.Execute(rsql) + assert.Nil(t, err) + strm := streamsql.stream + + // 创建结果接收通道 + resultChan := make(chan interface{}, 10) + + // 添加结果回调 + strm.AddSink(func(result interface{}) { + resultChan <- result + }) + + // 添加测试数据 + testData := []interface{}{ + map[string]interface{}{"deviceId": "sensor001", "deviceType": "temperature"}, + map[string]interface{}{"deviceId": "device002", "deviceType": "humidity"}, + map[string]interface{}{"deviceId": "sensor003", "deviceType": "pressure"}, + map[string]interface{}{"deviceId": "pump004", "deviceType": "actuator"}, + } + + // 添加数据 + for _, data := range testData { + strm.Emit(data) + } + + // 等待并收集结果 + var results []interface{} + timeout := time.After(2 * time.Second) + done := false + + for !done && len(results) < 2 { + select { + case result := <-resultChan: + results = append(results, result) + case <-timeout: + done = true + } + } + + // 验证结果:应该只有sensor001和sensor003匹配 + assert.GreaterOrEqual(t, len(results), 1, "应该收到至少一个匹配结果") + + // 验证结果中只包含以"sensor"开头的设备 + for _, result := range results { + resultSlice, ok := result.([]map[string]interface{}) + require.True(t, ok, "结果应该是[]map[string]interface{}类型") + + for _, item := range resultSlice { + deviceId, _ := item["deviceId"].(string) + assert.True(t, strings.HasPrefix(deviceId, "sensor"), + fmt.Sprintf("设备ID %s 应该以'sensor'开头", deviceId)) + } + } + }) + + // 测试场景2:后缀匹配 + t.Run("后缀匹配(%suffix)", func(t *testing.T) { + streamsql := New() + defer streamsql.Stop() + + var rsql = "SELECT deviceId, status FROM stream WHERE status LIKE '%error'" + err := streamsql.Execute(rsql) + assert.Nil(t, err) + strm := streamsql.stream + + resultChan := make(chan interface{}, 10) + strm.AddSink(func(result interface{}) { + resultChan <- result + }) + + testData := []interface{}{ + map[string]interface{}{"deviceId": "dev1", "status": "connection_error"}, + map[string]interface{}{"deviceId": "dev2", "status": "running"}, + map[string]interface{}{"deviceId": "dev3", "status": "timeout_error"}, + map[string]interface{}{"deviceId": "dev4", "status": "normal"}, + } + + for _, data := range testData { + strm.Emit(data) + } + + // 等待结果 + var results []interface{} + timeout := time.After(2 * time.Second) + done := false + + for !done && len(results) < 2 { + select { + case result := <-resultChan: + results = append(results, result) + case <-timeout: + done = true + } + } + + // 验证结果:应该只有以"error"结尾的状态 + assert.GreaterOrEqual(t, len(results), 1, "应该收到至少一个匹配结果") + + for _, result := range results { + resultSlice, ok := result.([]map[string]interface{}) + require.True(t, ok, "结果应该是[]map[string]interface{}类型") + + for _, item := range resultSlice { + status, _ := item["status"].(string) + assert.True(t, strings.HasSuffix(status, "error"), + fmt.Sprintf("状态 %s 应该以'error'结尾", status)) + } + } + }) + + // 测试场景3:包含匹配 + t.Run("包含匹配(%substring%)", func(t *testing.T) { + streamsql := New() + defer streamsql.Stop() + + var rsql = "SELECT deviceId, message FROM stream WHERE message LIKE '%alert%'" + err := streamsql.Execute(rsql) + assert.Nil(t, err) + strm := streamsql.stream + + resultChan := make(chan interface{}, 10) + strm.AddSink(func(result interface{}) { + resultChan <- result + }) + + testData := []interface{}{ + map[string]interface{}{"deviceId": "dev1", "message": "system alert: high temperature"}, + map[string]interface{}{"deviceId": "dev2", "message": "normal operation"}, + map[string]interface{}{"deviceId": "dev3", "message": "critical alert detected"}, + map[string]interface{}{"deviceId": "dev4", "message": "info: device startup"}, + } + + for _, data := range testData { + strm.Emit(data) + } + + // 等待结果 + var results []interface{} + timeout := time.After(2 * time.Second) + done := false + + for !done && len(results) < 2 { + select { + case result := <-resultChan: + results = append(results, result) + case <-timeout: + done = true + } + } + + // 验证结果:应该只有包含"alert"的消息 + assert.GreaterOrEqual(t, len(results), 1, "应该收到至少一个匹配结果") + + for _, result := range results { + resultSlice, ok := result.([]map[string]interface{}) + require.True(t, ok, "结果应该是[]map[string]interface{}类型") + + for _, item := range resultSlice { + message, _ := item["message"].(string) + assert.True(t, strings.Contains(message, "alert"), + fmt.Sprintf("消息 %s 应该包含'alert'", message)) + } + } + }) + + // 测试场景4:单字符通配符 + t.Run("单字符通配符(_)", func(t *testing.T) { + streamsql := New() + defer streamsql.Stop() + + var rsql = "SELECT deviceId, code FROM stream WHERE code LIKE 'E_0_'" + err := streamsql.Execute(rsql) + assert.Nil(t, err) + strm := streamsql.stream + + resultChan := make(chan interface{}, 10) + strm.AddSink(func(result interface{}) { + resultChan <- result + }) + + testData := []interface{}{ + map[string]interface{}{"deviceId": "dev1", "code": "E101"}, + map[string]interface{}{"deviceId": "dev2", "code": "E202"}, + map[string]interface{}{"deviceId": "dev3", "code": "E305"}, + map[string]interface{}{"deviceId": "dev4", "code": "F101"}, + } + + for _, data := range testData { + strm.Emit(data) + } + + // 等待结果 + var results []interface{} + timeout := time.After(2 * time.Second) + done := false + + for !done && len(results) < 2 { + select { + case result := <-resultChan: + results = append(results, result) + case <-timeout: + done = true + } + } + + // 验证结果:应该只有E_0_模式的代码(E101, E202不匹配E_0_,只有E305也不完全匹配) + // 实际上,根据模式E_0_,应该匹配如E101, E202等,让我们调整测试数据 + assert.GreaterOrEqual(t, len(results), 0, "根据通配符模式可能有匹配结果") + }) + + // 测试场景5:复杂模式 + t.Run("复杂LIKE模式", func(t *testing.T) { + streamsql := New() + defer streamsql.Stop() + + var rsql = "SELECT deviceId, filename FROM stream WHERE filename LIKE '%.log'" + err := streamsql.Execute(rsql) + assert.Nil(t, err) + strm := streamsql.stream + + resultChan := make(chan interface{}, 10) + strm.AddSink(func(result interface{}) { + resultChan <- result + }) + + testData := []interface{}{ + map[string]interface{}{"deviceId": "dev1", "filename": "system.log"}, + map[string]interface{}{"deviceId": "dev2", "filename": "config.txt"}, + map[string]interface{}{"deviceId": "dev3", "filename": "error.log"}, + map[string]interface{}{"deviceId": "dev4", "filename": "backup.bak"}, + } + + for _, data := range testData { + strm.Emit(data) + } + + // 等待结果 + var results []interface{} + timeout := time.After(2 * time.Second) + done := false + + for !done && len(results) < 2 { + select { + case result := <-resultChan: + results = append(results, result) + case <-timeout: + done = true + } + } + + // 验证结果:应该只有.log文件 + assert.GreaterOrEqual(t, len(results), 1, "应该收到至少一个匹配结果") + + for _, result := range results { + resultSlice, ok := result.([]map[string]interface{}) + require.True(t, ok, "结果应该是[]map[string]interface{}类型") + + for _, item := range resultSlice { + filename, _ := item["filename"].(string) + assert.True(t, strings.HasSuffix(filename, ".log"), + fmt.Sprintf("文件名 %s 应该以'.log'结尾", filename)) + } + } + }) + + // 测试场景6:在聚合查询中使用LIKE + t.Run("聚合查询中的LIKE", func(t *testing.T) { + streamsql := New() + defer streamsql.Stop() + + var rsql = "SELECT deviceType, count(*) as device_count FROM stream WHERE deviceId LIKE 'sensor%' GROUP BY deviceType" + err := streamsql.Execute(rsql) + assert.Nil(t, err) + strm := streamsql.stream + + resultChan := make(chan interface{}, 10) + strm.AddSink(func(result interface{}) { + resultChan <- result + }) + + testData := []interface{}{ + map[string]interface{}{"deviceId": "sensor001", "deviceType": "temperature"}, + map[string]interface{}{"deviceId": "sensor002", "deviceType": "temperature"}, + map[string]interface{}{"deviceId": "device003", "deviceType": "temperature"}, + map[string]interface{}{"deviceId": "sensor004", "deviceType": "humidity"}, + map[string]interface{}{"deviceId": "pump005", "deviceType": "actuator"}, + } + + for _, data := range testData { + strm.Emit(data) + } + + // 等待聚合 + time.Sleep(500 * time.Millisecond) + strm.Window.Trigger() + + // 等待结果 + ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) + defer cancel() + + var actual interface{} + select { + case actual = <-resultChan: + cancel() + case <-ctx.Done(): + t.Fatal("测试超时,未收到聚合结果") + } + + // 验证聚合结果 + resultSlice, ok := actual.([]map[string]interface{}) + require.True(t, ok, "结果应该是[]map[string]interface{}类型") + + // 应该有两种设备类型:temperature(2个sensor), humidity(1个sensor) + assert.GreaterOrEqual(t, len(resultSlice), 1, "应该有至少一种设备类型的聚合结果") + + for _, result := range resultSlice { + deviceType, _ := result["deviceType"].(string) + count, ok := result["device_count"].(float64) + assert.True(t, ok, "device_count应该是float64类型") + assert.Greater(t, count, 0.0, "设备数量应该大于0") + + // 验证设备类型 + assert.True(t, deviceType == "temperature" || deviceType == "humidity", + fmt.Sprintf("设备类型 %s 应该是temperature或humidity", deviceType)) + } + }) + + // 测试场景7:HAVING子句中的LIKE + t.Run("HAVING子句中的LIKE", func(t *testing.T) { + streamsql := New() + defer streamsql.Stop() + + var rsql = "SELECT deviceType, max(temperature) as max_temp FROM stream GROUP BY deviceType HAVING deviceType LIKE '%temp%'" + err := streamsql.Execute(rsql) + assert.Nil(t, err) + strm := streamsql.stream + + resultChan := make(chan interface{}, 10) + strm.AddSink(func(result interface{}) { + resultChan <- result + }) + + testData := []interface{}{ + map[string]interface{}{"deviceType": "temperature_sensor", "temperature": 25.0}, + map[string]interface{}{"deviceType": "temperature_sensor", "temperature": 30.0}, + map[string]interface{}{"deviceType": "humidity_sensor", "temperature": 22.0}, + map[string]interface{}{"deviceType": "pressure_gauge", "temperature": 20.0}, + } + + for _, data := range testData { + strm.Emit(data) + } + + // 等待聚合 + time.Sleep(500 * time.Millisecond) + strm.Window.Trigger() + + // 等待结果 + ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) + defer cancel() + + var actual interface{} + select { + case actual = <-resultChan: + cancel() + case <-ctx.Done(): + t.Fatal("测试超时,未收到HAVING+LIKE结果") + } + + // 验证HAVING + LIKE结果 + resultSlice, ok := actual.([]map[string]interface{}) + require.True(t, ok, "结果应该是[]map[string]interface{}类型") + + // 应该只有包含"temp"的设备类型 + for _, result := range resultSlice { + deviceType, _ := result["deviceType"].(string) + assert.True(t, strings.Contains(deviceType, "temp"), + fmt.Sprintf("设备类型 %s 应该包含'temp'", deviceType)) + + maxTemp, ok := result["max_temp"].(float64) + assert.True(t, ok, "max_temp应该是float64类型") + assert.Greater(t, maxTemp, 0.0, "最大温度应该大于0") + } + }) +} + +// TestLikeFunctionEquivalence 测试LIKE语法与现有字符串函数的等价性 +func TestLikeFunctionEquivalence(t *testing.T) { + // 简化测试,重点验证LIKE功能已经正常工作 + t.Run("LIKE语法工作正常验证", func(t *testing.T) { + streamsql := New() + defer streamsql.Stop() + + // 使用LIKE的查询 + var likeSQL = "SELECT deviceId FROM stream WHERE deviceId LIKE 'sensor%'" + err := streamsql.Execute(likeSQL) + assert.Nil(t, err) + + resultChan := make(chan interface{}, 10) + streamsql.stream.AddSink(func(result interface{}) { + resultChan <- result + }) + + // 测试数据 + testData := []interface{}{ + map[string]interface{}{"deviceId": "sensor001"}, + map[string]interface{}{"deviceId": "device002"}, + map[string]interface{}{"deviceId": "sensor003"}, + } + + // 添加数据 + for _, data := range testData { + streamsql.stream.Emit(data) + } + + // 收集结果 + timeout := time.After(2 * time.Second) + var results []interface{} + + for len(results) < 2 { + select { + case result := <-resultChan: + results = append(results, result) + case <-timeout: + break + } + } + + // 验证LIKE查询返回了预期的结果 + assert.Equal(t, 2, len(results), "LIKE查询应该返回2个匹配'sensor%'的结果") + t.Logf("LIKE查询成功返回%d个结果", len(results)) + + // 验证返回的结果确实是以'sensor'开头的 + for i, result := range results { + resultSlice, ok := result.([]map[string]interface{}) + assert.True(t, ok, fmt.Sprintf("结果%d应该是[]map[string]interface{}类型", i)) + if len(resultSlice) > 0 { + deviceId, exists := resultSlice[0]["deviceId"] + assert.True(t, exists, "结果应该包含deviceId字段") + deviceIdStr, ok := deviceId.(string) + assert.True(t, ok, "deviceId应该是字符串类型") + assert.True(t, strings.HasPrefix(deviceIdStr, "sensor"), + fmt.Sprintf("deviceId '%s' 应该以'sensor'开头", deviceIdStr)) + } + } + }) +} + +// TestLikePatternMatching 测试LIKE模式匹配算法的正确性 +func TestLikePatternMatching(t *testing.T) { + // 这些是单元测试,直接测试LIKE匹配函数 + tests := []struct { + text string + pattern string + expected bool + desc string + }{ + // 前缀匹配测试 + {"hello", "hello%", true, "精确前缀匹配"}, + {"hello world", "hello%", true, "前缀匹配"}, + {"hi there", "hello%", false, "前缀不匹配"}, + {"", "%", true, "空字符串匹配任意模式"}, + + // 后缀匹配测试 + {"test.log", "%.log", true, "后缀匹配"}, + {"test.txt", "%.log", false, "后缀不匹配"}, + + // 包含匹配测试 + {"hello world test", "%world%", true, "包含匹配"}, + {"hello test", "%world%", false, "不包含"}, + + // 单字符通配符测试 + {"abc", "a_c", true, "单字符通配符匹配"}, + {"aXc", "a_c", true, "单字符通配符匹配任意字符"}, + {"abbc", "a_c", false, "单字符通配符不匹配多个字符"}, + + // 复杂模式测试 + {"file123.log", "file___.log", true, "多个单字符通配符"}, + {"file12.log", "file___.log", false, "字符数不匹配"}, + {"prefix_test_suffix", "prefix%suffix", true, "前后缀组合"}, + + // 边界情况测试 + {"", "", true, "空模式匹配空字符串"}, + {"abc", "", false, "非空字符串不匹配空模式"}, + {"", "abc", false, "空字符串不匹配非空模式"}, + {"abc", "abc", true, "完全匹配"}, + } + + for _, test := range tests { + t.Run(test.desc, func(t *testing.T) { + // 直接使用内部函数进行测试 + // 注意:这里我们需要通过SQL查询来测试,因为匹配函数是内部的 + streamsql := New() + defer streamsql.Stop() + + // 构造SQL查询 + rsql := fmt.Sprintf("SELECT value FROM stream WHERE value LIKE '%s'", test.pattern) + err := streamsql.Execute(rsql) + assert.Nil(t, err) + + resultChan := make(chan interface{}, 10) + streamsql.stream.AddSink(func(result interface{}) { + resultChan <- result + }) + + // 添加测试数据 + testData := map[string]interface{}{"value": test.text} + streamsql.stream.Emit(testData) + + // 等待结果 + timeout := time.After(1 * time.Second) + var hasResult bool + + select { + case result := <-resultChan: + resultSlice, ok := result.([]map[string]interface{}) + hasResult = ok && len(resultSlice) > 0 + case <-timeout: + hasResult = false + } + + if test.expected { + assert.True(t, hasResult, fmt.Sprintf("模式'%s'应该匹配文本'%s'", test.pattern, test.text)) + } else { + assert.False(t, hasResult, fmt.Sprintf("模式'%s'不应该匹配文本'%s'", test.pattern, test.text)) + } + }) + } +} diff --git a/streamsql_plugin_test.go b/streamsql_plugin_test.go index 6b7a898..0764f49 100644 --- a/streamsql_plugin_test.go +++ b/streamsql_plugin_test.go @@ -104,7 +104,7 @@ func testStringFunctionsOnly(t *testing.T) { "phone": "13812345678", } - streamsql.AddData(testData) + streamsql.Emit(testData) time.Sleep(300 * time.Millisecond) select { @@ -149,7 +149,7 @@ func testConversionFunctionsOnly(t *testing.T) { "user_id": "12345", } - streamsql.AddData(testData) + streamsql.Emit(testData) time.Sleep(300 * time.Millisecond) select { @@ -205,7 +205,7 @@ func testMathFunctionsInAggregate(t *testing.T) { } for _, data := range testData { - streamsql.AddData(data) + streamsql.Emit(data) } time.Sleep(1 * time.Second) @@ -269,7 +269,7 @@ func TestRuntimeFunctionManagement(t *testing.T) { resultChan <- result }) - streamsql.AddData(map[string]interface{}{"value": "test"}) + streamsql.Emit(map[string]interface{}{"value": "test"}) time.Sleep(300 * time.Millisecond) select { @@ -390,7 +390,7 @@ func TestCompleteSQLIntegration(t *testing.T) { "amount": 100.0, } - streamsql.AddData(testData) + streamsql.Emit(testData) time.Sleep(300 * time.Millisecond) select { diff --git a/streamsql_test.go b/streamsql_test.go index 93d64a4..c36e374 100644 --- a/streamsql_test.go +++ b/streamsql_test.go @@ -70,8 +70,8 @@ func TestStreamData(t *testing.T) { "humidity": 50.0 + rand.Float64()*20, // 湿度范围: 50-70% } // 将数据添加到流中,触发 StreamSQL 的实时处理 - // AddData 会将数据分发到相应的窗口和聚合器中 - ssql.stream.AddData(randomData) + // Emit 会将数据分发到相应的窗口和聚合器中 + ssql.Emit(randomData) } case <-ctx.Done(): @@ -131,7 +131,7 @@ func TestStreamsql(t *testing.T) { } for _, data := range testData { - strm.AddData(data) + strm.Emit(data) } // 捕获结果 resultChan := make(chan interface{}) @@ -201,7 +201,7 @@ func TestStreamsqlWithoutGroupBy(t *testing.T) { } for _, data := range testData { - strm.AddData(data) + strm.Emit(data) } // 捕获结果 resultChan := make(chan interface{}) @@ -272,7 +272,7 @@ func TestStreamsqlDistinct(t *testing.T) { // 添加数据 //fmt.Println("添加测试数据") for _, data := range testData { - strm.AddData(data) + strm.Emit(data) } // 创建结果接收通道 @@ -374,7 +374,7 @@ func TestStreamsqlLimit(t *testing.T) { // 实时验证:添加一条数据,立即验证一条结果 for i, data := range testData { // 添加数据 - strm.AddData(data) + strm.Emit(data) // 立即等待并验证结果 ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second) @@ -438,7 +438,7 @@ func TestStreamsqlLimit(t *testing.T) { // 添加数据 for _, data := range testData { - strm.AddData(data) + strm.Emit(data) } // 等待聚合 @@ -513,7 +513,7 @@ func TestStreamsqlLimit(t *testing.T) { // 添加数据 for _, data := range testData { - strm.AddData(data) + strm.Emit(data) } // 等待窗口触发 @@ -586,7 +586,7 @@ func TestStreamsqlLimit(t *testing.T) { // 添加数据 for _, data := range testData { - strm.AddData(data) + strm.Emit(data) } // 等待聚合 @@ -657,7 +657,7 @@ func TestSimpleQuery(t *testing.T) { // 发送数据 //fmt.Println("添加数据...") for _, data := range testData { - strm.AddData(data) + strm.Emit(data) } // 等待结果 @@ -713,7 +713,7 @@ func TestHavingClause(t *testing.T) { // 添加数据 for _, data := range testData { - strm.AddData(data) + strm.Emit(data) } // 等待窗口初始化 @@ -796,7 +796,7 @@ func TestSessionWindow(t *testing.T) { if item.wait > 0 { time.Sleep(item.wait) } - strm.AddData(item.data) + strm.Emit(item.data) } // 等待会话超时,使最后一个会话触发 @@ -889,7 +889,7 @@ func TestExpressionInAggregation(t *testing.T) { // 添加数据 //fmt.Println("添加测试数据") for _, data := range testData { - strm.AddData(data) + strm.Emit(data) } // 创建结果接收通道 @@ -974,7 +974,7 @@ func TestAdvancedFunctionsInSQL(t *testing.T) { // 添加数据 //fmt.Println("添加测试数据") for _, data := range testData { - strm.AddData(data) + strm.Emit(data) } // 创建结果接收通道 @@ -1073,7 +1073,7 @@ func TestCustomFunctionInSQL(t *testing.T) { // 添加数据 //fmt.Println("添加测试数据") for _, data := range testData { - strm.AddData(data) + strm.Emit(data) } // 创建结果接收通道 @@ -1158,7 +1158,7 @@ func TestNewAggregateFunctionsInSQL(t *testing.T) { // 添加数据 //fmt.Println("添加测试数据") for _, data := range testData { - strm.AddData(data) + strm.Emit(data) } // 创建结果接收通道 @@ -1268,7 +1268,7 @@ func TestStatisticalAggregateFunctionsInSQL(t *testing.T) { // 添加数据 //fmt.Println("添加测试数据") for _, data := range testData { - strm.AddData(data) + strm.Emit(data) } // 创建结果接收通道 @@ -1370,7 +1370,7 @@ func TestDeduplicateAggregateInSQL(t *testing.T) { // 添加数据 //fmt.Println("添加测试数据") for _, data := range testData { - strm.AddData(data) + strm.Emit(data) } // 创建结果接收通道 @@ -1480,7 +1480,7 @@ func TestExprAggregationFunctions(t *testing.T) { // 添加数据 //fmt.Println("添加测试数据") for _, data := range testData { - strm.AddData(data) + strm.Emit(data) } // 创建结果接收通道 @@ -1637,7 +1637,7 @@ func TestAnalyticalFunctionsInSQL(t *testing.T) { // 添加数据 //fmt.Println("添加测试数据") for _, data := range testData { - strm.AddData(data) + strm.Emit(data) } // 创建结果接收通道 @@ -1734,7 +1734,7 @@ func TestLagFunctionInSQL(t *testing.T) { //fmt.Println("添加测试数据:", testData) for _, data := range testData { //fmt.Printf("添加第%d个数据: temperature=%.1f\n", i+1, data.(map[string]interface{})["temperature"]) - strm.AddData(data) + strm.Emit(data) time.Sleep(100 * time.Millisecond) // 稍微延迟确保顺序 } @@ -1832,7 +1832,7 @@ func TestHadChangedFunctionInSQL(t *testing.T) { // 添加数据 //fmt.Println("添加测试数据") for _, data := range testData { - strm.AddData(data) + strm.Emit(data) } // 创建结果接收通道 @@ -1912,7 +1912,7 @@ func TestLatestFunctionInSQL(t *testing.T) { // 添加数据 //fmt.Println("添加测试数据") for _, data := range testData { - strm.AddData(data) + strm.Emit(data) } // 创建结果接收通道 @@ -2004,7 +2004,7 @@ func TestChangedColFunctionInSQL(t *testing.T) { // 添加数据 //fmt.Println("添加测试数据") for _, data := range testData { - strm.AddData(data) + strm.Emit(data) } // 创建结果接收通道 @@ -2085,7 +2085,7 @@ func TestAnalyticalFunctionsIncrementalComputation(t *testing.T) { // 添加数据 //fmt.Println("添加测试数据") for _, data := range testData { - strm.AddData(data) + strm.Emit(data) } // 创建结果接收通道 @@ -2184,7 +2184,7 @@ func TestIncrementalComputationBasic(t *testing.T) { // 添加数据 //fmt.Println("添加测试数据") for _, data := range testData { - strm.AddData(data) + strm.Emit(data) } // 创建结果接收通道 @@ -2288,7 +2288,7 @@ func TestExprFunctions(t *testing.T) { // 添加数据 for _, data := range testData { - strm.AddData(data) + strm.Emit(data) } // 等待结果 @@ -2374,7 +2374,7 @@ func TestExprFunctionsInAggregation(t *testing.T) { // 添加数据 for _, data := range testData { - strm.AddData(data) + strm.Emit(data) } // 等待窗口初始化 @@ -2450,7 +2450,7 @@ func TestNestedExprFunctions(t *testing.T) { // 添加数据 for _, data := range testData { - strm.AddData(data) + strm.Emit(data) } // 等待结果 @@ -2540,7 +2540,7 @@ func TestExprFunctionsWithStreamSQLFunctions(t *testing.T) { // 添加数据 for _, data := range testData { - strm.AddData(data) + strm.Emit(data) } // 等待结果 @@ -2622,7 +2622,7 @@ func TestSelectAllFeature(t *testing.T) { } // 发送数据 - strm.AddData(testData) + strm.Emit(testData) // 等待结果 ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) @@ -2682,7 +2682,7 @@ func TestSelectAllFeature(t *testing.T) { // 发送数据 for _, data := range testData { - strm.AddData(data) + strm.Emit(data) // 立即检查结果 ctx, cancel := context.WithTimeout(context.Background(), 500*time.Millisecond) @@ -2767,7 +2767,7 @@ func TestSelectAllFeature(t *testing.T) { // 发送数据 for _, data := range testData { - strm.AddData(data) + strm.Emit(data) // 立即检查结果 ctx, cancel := context.WithTimeout(context.Background(), 500*time.Millisecond) @@ -2840,7 +2840,7 @@ func TestSelectAllFeature(t *testing.T) { } // 发送数据 - strm.AddData(testData) + strm.Emit(testData) // 等待结果 ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) @@ -2896,7 +2896,7 @@ func TestCaseNullValueHandlingInAggregation(t *testing.T) { var results []map[string]interface{} resultChan := make(chan interface{}, 10) - ssql.Stream().AddSink(func(result interface{}) { + ssql.AddSink(func(result interface{}) { resultChan <- result }) @@ -2910,7 +2910,7 @@ func TestCaseNullValueHandlingInAggregation(t *testing.T) { } for _, data := range testData { - ssql.Stream().AddData(data) + ssql.Emit(data) } // 等待窗口触发