From 8ebd152ec98a609296e6371d46fbb9cad19e02fa Mon Sep 17 00:00:00 2001 From: rulego-team Date: Sun, 3 Aug 2025 23:39:50 +0800 Subject: [PATCH] =?UTF-8?q?refactor:Emit=E5=85=A5=E5=8F=82=E4=BB=8Einterfa?= =?UTF-8?q?ce{}=20=E6=94=B9=E6=88=90=20map[string]interface{};AddSink(func?= =?UTF-8?q?(results=20interface{})=E6=94=B9=E6=88=90AddSink(func(results?= =?UTF-8?q?=20[]map[string]interface{})?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 24 +- README_ZH.md | 26 +- examples/advanced-functions/main.go | 2 +- examples/complex-nested-access/main.go | 100 +- examples/comprehensive-test/main.go | 12 +- examples/custom-functions-demo/main.go | 36 +- examples/nested-field-examples/main.go | 141 ++- examples/non-aggregation/main.go | 12 +- examples/null-comparison-examples/main.go | 62 +- examples/persistence/main.go | 2 +- examples/simple-custom-functions/main.go | 18 +- examples/unified_config/demo.go | 2 +- examples/unified_config/window_config_demo.go | 2 +- stream/handler_data.go | 27 +- stream/handler_result.go | 8 +- stream/persistence.go | 66 +- stream/processor_data.go | 61 +- stream/stream.go | 36 +- stream/stream_factory.go | 4 +- stream/stream_field_test.go | 664 ++++++------- stream/stream_performance_test.go | 884 +++++++++--------- stream/stream_persistence_test.go | 2 +- stream/stream_test.go | 12 +- stream/stream_window_test.go | 488 +++++----- streamsql.go | 82 +- streamsql_benchmark_test.go | 14 +- streamsql_case_test.go | 44 +- streamsql_custom_functions_test.go | 24 +- streamsql_function_integration_test.go | 118 +-- streamsql_is_null_test.go | 18 +- streamsql_like_test.go | 98 +- streamsql_nested_field_test.go | 314 +++++++ streamsql_plugin_test.go | 16 +- streamsql_quoted_support_test.go | 40 +- streamsql_sync_sink_test.go | 63 +- streamsql_table_print_test.go | 100 +- streamsql_test.go | 360 +++---- 37 files changed, 2127 insertions(+), 1855 deletions(-) create mode 100644 streamsql_nested_field_test.go diff --git a/README.md b/README.md index 9daabfa..c4bc8b7 100644 --- a/README.md +++ b/README.md @@ -82,9 +82,9 @@ func main() { } // Handle real-time transformation results - ssql.AddSink(func(result interface{}) { - fmt.Printf("Real-time result: %+v\n", result) - }) + ssql.AddSink(func(results []map[string]interface{}) { + fmt.Printf("Real-time result: %+v\n", results) + }) // Simulate sensor data input sensorData := []map[string]interface{}{ @@ -195,8 +195,8 @@ func main() { "humidity": 50.0 + rand.Float64()*20, // Humidity range: 50-70% } // Add data to stream, triggering StreamSQL's real-time processing - // AddData distributes data to corresponding windows and aggregators - ssql.stream.AddData(randomData) + // Emit distributes data to corresponding windows and aggregators + ssql.Emit(randomData) } case <-ctx.Done(): @@ -209,10 +209,10 @@ func main() { // Step 6: Setup Result Processing Pipeline resultChan := make(chan interface{}) // Add computation result callback function (Sink) - // When window triggers computation, results are output through this callback - ssql.stream.AddSink(func(result interface{}) { - resultChan <- result - }) + // When window triggers computation, results are output through this callback + ssql.AddSink(func(results []map[string]interface{}) { + resultChan <- results + }) // Step 7: Start Result Consumer Goroutine // Count received results for effect verification @@ -273,9 +273,9 @@ func main() { } // Handle aggregation results - ssql.AddSink(func(result interface{}) { - fmt.Printf("Aggregation result: %+v\n", result) - }) + ssql.AddSink(func(results []map[string]interface{}) { + fmt.Printf("Aggregation result: %+v\n", results) + }) // Add nested structured data nestedData := map[string]interface{}{ diff --git a/README_ZH.md b/README_ZH.md index 49f20c6..18e5730 100644 --- a/README_ZH.md +++ b/README_ZH.md @@ -85,8 +85,8 @@ func main() { } // 处理实时转换结果 - ssql.AddSink(func(result interface{}) { - fmt.Printf("实时处理结果: %+v\n", result) + ssql.AddSink(func(results []map[string]interface{}) { + fmt.Printf("实时处理结果: %+v\n", results) }) // 模拟传感器数据输入 @@ -206,7 +206,7 @@ func main() { // - 按deviceId分组 // - 将数据分配到对应的时间窗口 // - 更新聚合计算状态 - ssql.stream.AddData(randomData) + ssql.Emit(randomData) } case <-ctx.Done(): @@ -221,8 +221,10 @@ func main() { // 6. 注册结果回调函数 // 当窗口触发时(每5秒),会调用这个回调函数 // 传递聚合计算的结果 - ssql.stream.AddSink(func(result interface{}) { - resultChan <- result + ssql.AddSink(func(results []map[string]interface{}) { + for _, result := range results { + resultChan <- result + } }) // 7. 结果消费者 - 处理计算结果 @@ -289,18 +291,22 @@ func main() { } // 处理聚合结果 - ssql.AddSink(func(result interface{}) { - fmt.Printf("聚合结果: %+v\n", result) + ssql.AddSink(func(results []map[string]interface{}) { + fmt.Printf("聚合结果: %+v\n", results) }) // 添加嵌套结构数据 nestedData := map[string]interface{}{ "device": map[string]interface{}{ "info": map[string]interface{}{ - "name": "temperature-sensor-001", - "type": "temperature", + "name": "temperature-sensor-001", + "type": "temperature", + "status": "active", + }, + "location": map[string]interface{}{ + "building": "智能温室-A区", + "floor": "3F", }, - "location": "智能温室-A区", }, "sensor": map[string]interface{}{ "temperature": 25.5, diff --git a/examples/advanced-functions/main.go b/examples/advanced-functions/main.go index ea0acdb..8cc9b7c 100644 --- a/examples/advanced-functions/main.go +++ b/examples/advanced-functions/main.go @@ -51,7 +51,7 @@ func main() { fmt.Println("✓ SQL执行成功") // 5. 添加结果监听器 - ssql.AddSink(func(result interface{}) { + ssql.AddSink(func(result []map[string]interface{}) { fmt.Printf("📊 聚合结果: %v\n", result) }) diff --git a/examples/complex-nested-access/main.go b/examples/complex-nested-access/main.go index 7d5f418..25e4c72 100644 --- a/examples/complex-nested-access/main.go +++ b/examples/complex-nested-access/main.go @@ -83,19 +83,17 @@ func demonstrateArrayAccess(ssql *streamsql.Streamsql) { wg.Add(1) // 设置结果回调 - ssql.AddSink(func(result interface{}) { + ssql.AddSink(func(result []map[string]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 i, item := range result { + 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() } }) @@ -168,20 +166,19 @@ func demonstrateMapKeyAccess(ssql *streamsql.Streamsql) { wg.Add(1) // 设置结果回调 - ssql.AddSink(func(result interface{}) { + ssql.AddSink(func(result []map[string]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() - } + resultSlice := result + 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() } }) @@ -266,20 +263,19 @@ func demonstrateComplexMixedAccess(ssql *streamsql.Streamsql) { wg.Add(1) // 设置结果回调 - ssql.AddSink(func(result interface{}) { + ssql.AddSink(func(result []map[string]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() - } + resultSlice := result + 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() } }) @@ -325,19 +321,18 @@ func demonstrateNegativeIndexAccess(ssql *streamsql.Streamsql) { wg.Add(1) // 设置结果回调 - ssql.AddSink(func(result interface{}) { + ssql.AddSink(func(result []map[string]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() - } + resultSlice := result + 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() } }) @@ -372,20 +367,19 @@ func demonstrateArrayIndexAggregation(ssql *streamsql.Streamsql) { wg.Add(1) // 设置结果回调 - ssql.AddSink(func(result interface{}) { + ssql.AddSink(func(result []map[string]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() - } + resultSlice := result + 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() } }) diff --git a/examples/comprehensive-test/main.go b/examples/comprehensive-test/main.go index 1949404..c7f2cb3 100644 --- a/examples/comprehensive-test/main.go +++ b/examples/comprehensive-test/main.go @@ -127,7 +127,7 @@ func testBasicFiltering() { } // 添加结果处理函数 - ssql.AddSink(func(result interface{}) { + ssql.AddSink(func(result []map[string]interface{}) { fmt.Printf(" 📊 高温告警: %v\n", result) }) @@ -172,7 +172,7 @@ func testAggregation() { } // 处理聚合结果 - ssql.AddSink(func(result interface{}) { + ssql.AddSink(func(result []map[string]interface{}) { fmt.Printf(" 📊 聚合结果: %v\n", result) }) @@ -221,7 +221,7 @@ func testSlidingWindow() { return } - ssql.AddSink(func(result interface{}) { + ssql.AddSink(func(result []map[string]interface{}) { fmt.Printf(" 📊 滑动窗口分析: %v\n", result) }) @@ -262,7 +262,7 @@ func testNestedFields() { return } - ssql.AddSink(func(result interface{}) { + ssql.AddSink(func(result []map[string]interface{}) { fmt.Printf(" 📊 嵌套字段结果: %v\n", result) }) @@ -336,7 +336,7 @@ func testCustomFunctions() { return } - ssql.AddSink(func(result interface{}) { + ssql.AddSink(func(result []map[string]interface{}) { fmt.Printf(" 📊 自定义函数结果: %v\n", result) }) @@ -396,7 +396,7 @@ func testComplexQuery() { return } - ssql.AddSink(func(result interface{}) { + ssql.AddSink(func(result []map[string]interface{}) { fmt.Printf(" 📊 复杂查询结果: %v\n", result) }) diff --git a/examples/custom-functions-demo/main.go b/examples/custom-functions-demo/main.go index 8ca9514..3daf2eb 100644 --- a/examples/custom-functions-demo/main.go +++ b/examples/custom-functions-demo/main.go @@ -609,14 +609,14 @@ func testMathFunctions(ssql *streamsql.Streamsql) { } // 添加测试数据 - testData := []interface{}{ - map[string]interface{}{ + testData := []map[string]interface{}{ + { "device": "sensor1", "temperature": 68.0, // 华氏度 "radius": 5.0, "x1": 0.0, "y1": 0.0, "x2": 3.0, "y2": 4.0, // 距离=5 }, - map[string]interface{}{ + { "device": "sensor1", "temperature": 86.0, // 华氏度 "radius": 10.0, @@ -625,7 +625,7 @@ func testMathFunctions(ssql *streamsql.Streamsql) { } // 添加结果监听器 - ssql.AddSink(func(result interface{}) { + ssql.AddSink(func(result []map[string]interface{}) { fmt.Printf(" 📊 数学函数结果: %v\n", result) }) @@ -659,20 +659,20 @@ func testStringFunctions(ssql *streamsql.Streamsql) { } // 添加测试数据 - testData := []interface{}{ - map[string]interface{}{ + testData := []map[string]interface{}{ + { "device": "sensor1", "metadata": `{"version":"1.0","type":"temperature"}`, "level": 3, }, - map[string]interface{}{ + { "device": "sensor2", "metadata": `{"version":"2.0","type":"humidity"}`, "level": 5, }, } - ssql.AddSink(func(result interface{}) { + ssql.AddSink(func(result []map[string]interface{}) { fmt.Printf(" 📊 字符串函数结果: %v\n", result) }) @@ -702,20 +702,20 @@ func testConversionFunctions(ssql *streamsql.Streamsql) { } // 添加测试数据 - testData := []interface{}{ - map[string]interface{}{ + testData := []map[string]interface{}{ + { "device": "server1", "client_ip": "192.168.1.100", "memory_usage": 1073741824, // 1GB }, - map[string]interface{}{ + { "device": "server2", "client_ip": "10.0.0.50", "memory_usage": 2147483648, // 2GB }, } - ssql.AddSink(func(result interface{}) { + ssql.AddSink(func(result []map[string]interface{}) { fmt.Printf(" 📊 转换函数结果: %v\n", result) }) @@ -746,14 +746,14 @@ func testAggregateFunctions(ssql *streamsql.Streamsql) { } // 添加测试数据 - testData := []interface{}{ - map[string]interface{}{"device": "sensor1", "value": 2.0, "category": "A"}, - map[string]interface{}{"device": "sensor1", "value": 8.0, "category": "A"}, - map[string]interface{}{"device": "sensor1", "value": 32.0, "category": "B"}, - map[string]interface{}{"device": "sensor1", "value": 128.0, "category": "A"}, + testData := []map[string]interface{}{ + {"device": "sensor1", "value": 2.0, "category": "A"}, + {"device": "sensor1", "value": 8.0, "category": "A"}, + {"device": "sensor1", "value": 32.0, "category": "B"}, + {"device": "sensor1", "value": 128.0, "category": "A"}, } - ssql.AddSink(func(result interface{}) { + ssql.AddSink(func(result []map[string]interface{}) { fmt.Printf(" 📊 聚合函数结果: %v\n", result) }) diff --git a/examples/nested-field-examples/main.go b/examples/nested-field-examples/main.go index 62beacd..2d265b6 100644 --- a/examples/nested-field-examples/main.go +++ b/examples/nested-field-examples/main.go @@ -119,19 +119,18 @@ func demonstrateBasicNestedAccess(ssql *streamsql.Streamsql) { wg.Add(1) // 设置结果回调 - ssql.AddSink(func(result interface{}) { + ssql.AddSink(func(result []map[string]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() - } + resultSlice := result + 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() } }) @@ -166,20 +165,19 @@ func demonstrateNestedAggregation(ssql *streamsql.Streamsql) { wg.Add(1) // 设置结果回调 - ssql.AddSink(func(result interface{}) { + ssql.AddSink(func(result []map[string]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() - } + resultSlice := result + 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() } }) @@ -271,19 +269,18 @@ func demonstrateArrayAccess(ssql *streamsql.Streamsql) { wg.Add(1) // 设置结果回调 - ssql.AddSink(func(result interface{}) { + ssql.AddSink(func(result []map[string]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() - } + resultSlice := result + 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() } }) @@ -356,20 +353,19 @@ func demonstrateMapKeyAccess(ssql *streamsql.Streamsql) { wg.Add(1) // 设置结果回调 - ssql.AddSink(func(result interface{}) { + ssql.AddSink(func(result []map[string]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() - } + resultSlice := result + 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() } }) @@ -454,20 +450,19 @@ func demonstrateComplexMixedAccess(ssql *streamsql.Streamsql) { wg.Add(1) // 设置结果回调 - ssql.AddSink(func(result interface{}) { + ssql.AddSink(func(result []map[string]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() - } + resultSlice := result + 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() } }) @@ -513,19 +508,18 @@ func demonstrateNegativeIndexAccess(ssql *streamsql.Streamsql) { wg.Add(1) // 设置结果回调 - ssql.AddSink(func(result interface{}) { + ssql.AddSink(func(result []map[string]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() - } + resultSlice := result + 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() } }) @@ -560,20 +554,19 @@ func demonstrateArrayIndexAggregation(ssql *streamsql.Streamsql) { wg.Add(1) // 设置结果回调 - ssql.AddSink(func(result interface{}) { + ssql.AddSink(func(result []map[string]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() - } + resultSlice := result + 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() } }) diff --git a/examples/non-aggregation/main.go b/examples/non-aggregation/main.go index 2162009..722fc64 100644 --- a/examples/non-aggregation/main.go +++ b/examples/non-aggregation/main.go @@ -62,7 +62,7 @@ func demonstrateDataCleaning() { } // 结果处理 - ssql.AddSink(func(result interface{}) { + ssql.AddSink(func(result []map[string]interface{}) { fmt.Printf(" 清洗后数据: %+v\n", result) }) @@ -103,7 +103,7 @@ func demonstrateDataEnrichment() { panic(err) } - ssql.AddSink(func(result interface{}) { + ssql.AddSink(func(result []map[string]interface{}) { fmt.Printf(" 富化后数据: %+v\n", result) }) @@ -147,7 +147,7 @@ func demonstrateRealTimeAlerting() { panic(err) } - ssql.AddSink(func(result interface{}) { + ssql.AddSink(func(result []map[string]interface{}) { fmt.Printf(" 🚨 告警事件: %+v\n", result) }) @@ -191,7 +191,7 @@ func demonstrateDataFormatConversion() { panic(err) } - ssql.AddSink(func(result interface{}) { + ssql.AddSink(func(result []map[string]interface{}) { fmt.Printf(" 格式转换结果: %+v\n", result) }) @@ -230,7 +230,7 @@ func demonstrateDataRouting() { panic(err) } - ssql.AddSink(func(result interface{}) { + ssql.AddSink(func(result []map[string]interface{}) { fmt.Printf(" 路由结果: %+v\n", result) }) @@ -273,7 +273,7 @@ func demonstrateNestedFieldProcessing() { panic(err) } - ssql.AddSink(func(result interface{}) { + ssql.AddSink(func(result []map[string]interface{}) { fmt.Printf(" 嵌套字段处理结果: %+v\n", result) }) diff --git a/examples/null-comparison-examples/main.go b/examples/null-comparison-examples/main.go index cc09376..992e3b7 100644 --- a/examples/null-comparison-examples/main.go +++ b/examples/null-comparison-examples/main.go @@ -35,11 +35,9 @@ func demo1() { panic(err) } - ssql.AddSink(func(result interface{}) { - if results, ok := result.([]map[string]interface{}); ok { - for _, data := range results { - fmt.Printf("发现空值数据: %+v\n", data) - } + ssql.AddSink(func(result []map[string]interface{}) { + for _, data := range result { + fmt.Printf("发现空值数据: %+v\n", data) } }) @@ -75,11 +73,9 @@ func demo2() { panic(err) } - ssql.AddSink(func(result interface{}) { - if results, ok := result.([]map[string]interface{}); ok { - for _, data := range results { - fmt.Printf("发现有效数据: %+v\n", data) - } + ssql.AddSink(func(result []map[string]interface{}) { + for _, data := range result { + fmt.Printf("发现有效数据: %+v\n", data) } }) @@ -115,16 +111,14 @@ func demo3() { 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) - } + ssql.AddSink(func(result []map[string]interface{}) { + for _, data := range result { + status := data["status"] + value := data["value"] + if status != nil { + fmt.Printf("状态非空的数据: %+v\n", data) + } else if value == nil { + fmt.Printf("值为空的数据: %+v\n", data) } } }) @@ -162,18 +156,16 @@ func demo4() { 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"] + ssql.AddSink(func(result []map[string]interface{}) { + for _, data := range result { + 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) - } + if value != nil && value.(float64) > 20 { + fmt.Printf("高值数据 (value > 20): %+v\n", data) + } else if status == nil && priority != nil { + fmt.Printf("状态异常但有优先级的数据: %+v\n", data) } } }) @@ -212,11 +204,9 @@ func demo5() { panic(err) } - ssql.AddSink(func(result interface{}) { - if results, ok := result.([]map[string]interface{}); ok { - for _, data := range results { - fmt.Printf("有位置信息的设备: %+v\n", data) - } + ssql.AddSink(func(result []map[string]interface{}) { + for _, data := range result { + fmt.Printf("有位置信息的设备: %+v\n", data) } }) diff --git a/examples/persistence/main.go b/examples/persistence/main.go index 002c6f1..7dd61a6 100644 --- a/examples/persistence/main.go +++ b/examples/persistence/main.go @@ -169,7 +169,7 @@ func testDataRecovery() { // 添加sink来接收恢复的数据 recoveredCount := 0 - stream.AddSink(func(data interface{}) { + stream.AddSink(func(data []map[string]interface{}) { recoveredCount++ if recoveredCount <= 5 { fmt.Printf("恢复数据 %d: %+v\n", recoveredCount, data) diff --git a/examples/simple-custom-functions/main.go b/examples/simple-custom-functions/main.go index 674b75d..f2e23d2 100644 --- a/examples/simple-custom-functions/main.go +++ b/examples/simple-custom-functions/main.go @@ -122,19 +122,19 @@ func testSimpleQuery(ssql *streamsql.Streamsql) { } // 添加结果监听器 - ssql.AddSink(func(result interface{}) { + ssql.AddSink(func(result []map[string]interface{}) { fmt.Printf(" 📊 简单查询结果: %v\n", result) }) // 添加测试数据 - testData := []interface{}{ - map[string]interface{}{ + testData := []map[string]interface{}{ + { "device": "sensor1", "value": 5.0, "temperature": 68.0, // 华氏度 "radius": 3.0, }, - map[string]interface{}{ + { "device": "sensor2", "value": 10.0, "temperature": 86.0, // 华氏度 @@ -171,25 +171,25 @@ func testAggregateQuery(ssql *streamsql.Streamsql) { } // 添加结果监听器 - ssql.AddSink(func(result interface{}) { + ssql.AddSink(func(result []map[string]interface{}) { fmt.Printf(" 📊 聚合查询结果: %v\n", result) }) // 添加测试数据 - testData := []interface{}{ - map[string]interface{}{ + testData := []map[string]interface{}{ + { "device": "sensor1", "value": 3.0, "temperature": 32.0, // 0°C "radius": 1.0, }, - map[string]interface{}{ + { "device": "sensor1", "value": 4.0, "temperature": 212.0, // 100°C "radius": 2.0, }, - map[string]interface{}{ + { "device": "sensor2", "value": 5.0, "temperature": 68.0, // 20°C diff --git a/examples/unified_config/demo.go b/examples/unified_config/demo.go index 637a71c..618e31c 100644 --- a/examples/unified_config/demo.go +++ b/examples/unified_config/demo.go @@ -184,7 +184,7 @@ func compareConfigurations() { func demonstrateRealTimeProcessing(s *stream.Stream) { // 设置数据接收器 - s.AddSink(func(data interface{}) { + s.AddSink(func(data []map[string]interface{}) { fmt.Printf(" 接收到处理结果: %v\n", data) }) diff --git a/examples/unified_config/window_config_demo.go b/examples/unified_config/window_config_demo.go index 81dfa89..271638a 100644 --- a/examples/unified_config/window_config_demo.go +++ b/examples/unified_config/window_config_demo.go @@ -46,7 +46,7 @@ func testWindowWithConfig(configName string, ssql *streamsql.Streamsql) { // 添加结果处理器 stream := ssql.Stream() if stream != nil { - stream.AddSink(func(result interface{}) { + stream.AddSink(func(result []map[string]interface{}) { fmt.Printf("📊 %s - 窗口结果: %v\n", configName, result) }) diff --git a/stream/handler_data.go b/stream/handler_data.go index fc0ca01..07f6bc0 100644 --- a/stream/handler_data.go +++ b/stream/handler_data.go @@ -18,7 +18,9 @@ func NewDataHandler(stream *Stream) *DataHandler { } // addDataBlocking 阻塞模式添加数据,保证零数据丢失 -func (s *Stream) addDataBlocking(data interface{}) { +// 参数: +// - data: 要处理的数据,必须是map[string]interface{}类型 +func (s *Stream) addDataBlocking(data map[string]interface{}) { if s.blockingTimeout <= 0 { // 无超时限制,永久阻塞直到成功 dataChan := s.safeGetDataChan() @@ -45,7 +47,9 @@ func (s *Stream) addDataBlocking(data interface{}) { } // addDataWithExpansion 动态扩容模式 -func (s *Stream) addDataWithExpansion(data interface{}) { +// 参数: +// - data: 要处理的数据,必须是map[string]interface{}类型 +func (s *Stream) addDataWithExpansion(data map[string]interface{}) { // 首次尝试添加数据 if s.safeSendToDataChan(data) { return @@ -66,7 +70,9 @@ func (s *Stream) addDataWithExpansion(data interface{}) { } // addDataWithPersistence 持久化模式 -func (s *Stream) addDataWithPersistence(data interface{}) { +// 参数: +// - data: 要处理的数据,必须是map[string]interface{}类型 +func (s *Stream) addDataWithPersistence(data map[string]interface{}) { // 首次尝试添加数据 if s.safeSendToDataChan(data) { return @@ -90,7 +96,9 @@ func (s *Stream) addDataWithPersistence(data interface{}) { } // addDataWithDrop 丢弃模式 -func (s *Stream) addDataWithDrop(data interface{}) { +// 参数: +// - data: 要处理的数据,必须是map[string]interface{}类型 +func (s *Stream) addDataWithDrop(data map[string]interface{}) { // 智能非阻塞添加,分层背压控制 if s.safeSendToDataChan(data) { return @@ -180,14 +188,14 @@ func (s *Stream) addDataWithDrop(data interface{}) { } // safeGetDataChan 线程安全地获取dataChan引用 -func (s *Stream) safeGetDataChan() chan interface{} { +func (s *Stream) safeGetDataChan() chan map[string]interface{} { s.dataChanMux.RLock() defer s.dataChanMux.RUnlock() return s.dataChan } // safeSendToDataChan 线程安全地向dataChan发送数据 -func (s *Stream) safeSendToDataChan(data interface{}) bool { +func (s *Stream) safeSendToDataChan(data map[string]interface{}) bool { dataChan := s.safeGetDataChan() select { case dataChan <- data: @@ -230,7 +238,7 @@ func (s *Stream) expandDataChannel() { logger.Debug("Dynamic expansion of data channel: %d -> %d", oldCap, newCap) // 创建新的更大的通道 - newChan := make(chan interface{}, newCap) + newChan := make(chan map[string]interface{}, newCap) // 使用写锁安全地迁移数据 s.dataChanMux.Lock() @@ -269,7 +277,10 @@ migration_done: } // persistAndRetryData 持久化数据并重试 (改进版本,具备指数退避和资源控制) -func (s *Stream) persistAndRetryData(data interface{}) { +// persistAndRetryData 持久化并重试数据 +// 参数: +// - data: 要持久化的数据,必须是map[string]interface{}类型 +func (s *Stream) persistAndRetryData(data map[string]interface{}) { // 检查活跃重试协程数量,防止资源泄漏 currentRetries := atomic.LoadInt32(&s.activeRetries) if currentRetries >= s.maxRetryRoutines { diff --git a/stream/handler_result.go b/stream/handler_result.go index 9c65a16..a384a30 100644 --- a/stream/handler_result.go +++ b/stream/handler_result.go @@ -117,7 +117,7 @@ func (s *Stream) callSinksAsync(results []map[string]interface{}) { } // submitSinkTask 提交Sink任务 -func (s *Stream) submitSinkTask(sink func(interface{}), results []map[string]interface{}) { +func (s *Stream) submitSinkTask(sink func([]map[string]interface{}), results []map[string]interface{}) { // 捕获sink变量,避免闭包问题 currentSink := sink @@ -143,13 +143,15 @@ func (s *Stream) submitSinkTask(sink func(interface{}), results []map[string]int } // AddSink 添加Sink函数 -func (s *Stream) AddSink(sink func(interface{})) { +// 参数: +// - sink: 结果处理函数,接收[]map[string]interface{}类型的结果数据 +func (s *Stream) AddSink(sink func([]map[string]interface{})) { s.sinksMux.Lock() defer s.sinksMux.Unlock() s.sinks = append(s.sinks, sink) } // GetResultsChan 获取结果通道 -func (s *Stream) GetResultsChan() <-chan interface{} { +func (s *Stream) GetResultsChan() <-chan []map[string]interface{} { return s.resultChan } diff --git a/stream/persistence.go b/stream/persistence.go index 9fc3215..733fd96 100644 --- a/stream/persistence.go +++ b/stream/persistence.go @@ -14,21 +14,21 @@ import ( // PersistenceManager 数据持久化管理器 type PersistenceManager struct { - dataDir string // 持久化数据目录 - maxFileSize int64 // 单个文件最大大小(字节) - flushInterval time.Duration // 刷新间隔 - currentFile *os.File // 当前写入文件 - currentSize int64 // 当前文件大小 - fileIndex int // 文件索引 - writeMutex sync.Mutex // 写入互斥锁 - flushTimer *time.Timer // 刷新定时器 - pendingData []interface{} // 待写入数据 - pendingMutex sync.Mutex // 待写入数据互斥锁 - isRunning bool // 是否运行中 - runningMutex sync.RWMutex // 保护isRunning字段的读写锁 - stopChan chan struct{} // 停止通道 + dataDir string // 持久化数据目录 + maxFileSize int64 // 单个文件最大大小(字节) + flushInterval time.Duration // 刷新间隔 + currentFile *os.File // 当前写入文件 + currentSize int64 // 当前文件大小 + fileIndex int // 文件索引 + writeMutex sync.Mutex // 写入互斥锁 + flushTimer *time.Timer // 刷新定时器 + pendingData []map[string]interface{} // 待写入数据,类型安全 + pendingMutex sync.Mutex // 待写入数据互斥锁 + isRunning bool // 是否运行中 + runningMutex sync.RWMutex // 保护isRunning字段的读写锁 + stopChan chan struct{} // 停止通道 - // 统计信息 (新增) + // 统计信息 totalPersisted int64 totalLoaded int64 filesCreated int64 @@ -41,7 +41,7 @@ func NewPersistenceManager(dataDir string) *PersistenceManager { maxFileSize: 10 * 1024 * 1024, // 10MB per file flushInterval: 5 * time.Second, // 5秒刷新一次 fileIndex: 0, - pendingData: make([]interface{}, 0), + pendingData: make([]map[string]interface{}, 0), stopChan: make(chan struct{}), } @@ -60,7 +60,7 @@ func NewPersistenceManagerWithConfig(dataDir string, maxFileSize int64, flushInt maxFileSize: maxFileSize, flushInterval: flushInterval, fileIndex: 0, - pendingData: make([]interface{}, 0), + pendingData: make([]map[string]interface{}, 0), stopChan: make(chan struct{}), } @@ -147,7 +147,9 @@ func (pm *PersistenceManager) Stop() error { } // PersistData 持久化单条数据 -func (pm *PersistenceManager) PersistData(data interface{}) error { +// 参数: +// - data: 要持久化的数据,必须是map[string]interface{}类型 +func (pm *PersistenceManager) PersistData(data map[string]interface{}) error { // 检查是否正在运行 pm.runningMutex.RLock() running := pm.isRunning @@ -165,13 +167,16 @@ func (pm *PersistenceManager) PersistData(data interface{}) error { } // LoadPersistedData 加载并删除持久化数据 -func (pm *PersistenceManager) LoadPersistedData() ([]interface{}, error) { +// 返回值: +// - []map[string]interface{}: 加载的数据列表,类型安全 +// - error: 加载过程中的错误 +func (pm *PersistenceManager) LoadPersistedData() ([]map[string]interface{}, error) { files, err := filepath.Glob(filepath.Join(pm.dataDir, "streamsql_overflow_*.log")) if err != nil { return nil, fmt.Errorf("failed to glob files: %w", err) } - var allData []interface{} + var allData []map[string]interface{} for _, filename := range files { data, err := pm.loadDataFromFile(filename) @@ -258,7 +263,9 @@ func (pm *PersistenceManager) createNewFile() error { // writeDataToFile 将数据写入文件 // 注意:此方法应该在writeMutex锁保护下调用 -func (pm *PersistenceManager) writeDataToFile(data interface{}) error { +// 参数: +// - data: 要写入的数据,必须是map[string]interface{}类型 +func (pm *PersistenceManager) writeDataToFile(data map[string]interface{}) error { if pm.currentFile == nil { return fmt.Errorf("no current file") } @@ -296,7 +303,7 @@ func (pm *PersistenceManager) writeDataToFile(data interface{}) error { // flushPendingData 刷新待写入数据 func (pm *PersistenceManager) flushPendingData() { pm.pendingMutex.Lock() - dataToWrite := make([]interface{}, len(pm.pendingData)) + dataToWrite := make([]map[string]interface{}, len(pm.pendingData)) copy(dataToWrite, pm.pendingData) pm.pendingData = pm.pendingData[:0] // 清空切片 pm.pendingMutex.Unlock() @@ -365,14 +372,19 @@ func (pm *PersistenceManager) backgroundProcessor() { } // loadDataFromFile 从文件加载数据 -func (pm *PersistenceManager) loadDataFromFile(filename string) ([]interface{}, error) { +// 参数: +// - filename: 要加载的文件名 +// 返回值: +// - []map[string]interface{}: 加载的数据列表,类型安全 +// - error: 加载过程中的错误 +func (pm *PersistenceManager) loadDataFromFile(filename string) ([]map[string]interface{}, error) { file, err := os.Open(filename) if err != nil { return nil, fmt.Errorf("failed to open file %s: %w", filename, err) } defer file.Close() - var data []interface{} + var data []map[string]interface{} scanner := bufio.NewScanner(file) for scanner.Scan() { @@ -384,9 +396,13 @@ func (pm *PersistenceManager) loadDataFromFile(filename string) ([]interface{}, continue } - // 提取实际数据 + // 提取实际数据并进行类型断言 if actualData, ok := record["data"]; ok { - data = append(data, actualData) + if dataMap, isMap := actualData.(map[string]interface{}); isMap { + data = append(data, dataMap) + } else { + logger.Error("Invalid data type in persistence file, expected map[string]interface{}") + } } } diff --git a/stream/processor_data.go b/stream/processor_data.go index 05367c3..9662bd1 100644 --- a/stream/processor_data.go +++ b/stream/processor_data.go @@ -3,9 +3,7 @@ package stream import ( "encoding/json" "fmt" - "reflect" "strings" - "sync/atomic" "time" "github.com/rulego/streamsql/aggregator" @@ -94,18 +92,22 @@ func (dp *DataProcessor) registerExpressionCalculator(field string, fieldExpr ty currentFieldExpr.Expression, currentFieldExpr.Fields, func(data interface{}) (interface{}, error) { - return dp.evaluateExpressionForAggregation(currentFieldExpr, data) + // 确保数据是map[string]interface{}类型 + if dataMap, ok := data.(map[string]interface{}); ok { + return dp.evaluateExpressionForAggregation(currentFieldExpr, dataMap) + } + return nil, fmt.Errorf("unsupported data type: %T, expected map[string]interface{}", data) }, ) } // evaluateExpressionForAggregation 为聚合计算表达式 -func (dp *DataProcessor) evaluateExpressionForAggregation(fieldExpr types.FieldExpression, data interface{}) (interface{}, error) { - // 将数据转换为 map[string]interface{} 以便计算 - dataMap, err := dp.convertToDataMap(data) - if err != nil { - return nil, err - } +// 参数: +// - fieldExpr: 字段表达式 +// - data: 要处理的数据,必须是map[string]interface{}类型 +func (dp *DataProcessor) evaluateExpressionForAggregation(fieldExpr types.FieldExpression, data map[string]interface{}) (interface{}, error) { + // 直接使用传入的map数据 + dataMap := data // 检查表达式是否包含嵌套字段,如果有则直接使用自定义表达式引擎 hasNestedFields := strings.Contains(fieldExpr.Expression, ".") @@ -147,31 +149,7 @@ func (dp *DataProcessor) evaluateExpressionForAggregation(fieldExpr types.FieldE } // convertToDataMap 将数据转换为map格式 -func (dp *DataProcessor) convertToDataMap(data interface{}) (map[string]interface{}, error) { - switch d := data.(type) { - case map[string]interface{}: - return d, nil - default: - // 如果不是 map,尝试转换 - v := reflect.ValueOf(data) - if v.Kind() == reflect.Ptr { - v = v.Elem() - } - - if v.Kind() == reflect.Struct { - // 将结构体转换为 map - dataMap := make(map[string]interface{}) - t := v.Type() - for i := 0; i < t.NumField(); i++ { - field := t.Field(i) - dataMap[field.Name] = v.Field(i).Interface() - } - return dataMap, nil - } else { - return nil, fmt.Errorf("unsupported data type for expression: %T", data) - } - } -} +// convertToDataMap 方法已移除,请使用 github.com/rulego/streamsql/utils/converter.ToDataMap 函数替代 // evaluateNestedFieldExpression 计算嵌套字段表达式 func (dp *DataProcessor) evaluateNestedFieldExpression(expression string, dataMap map[string]interface{}) (interface{}, error) { @@ -440,14 +418,11 @@ func (dp *DataProcessor) applyHavingWithCondition(results []map[string]interface } // processDirectData 直接处理非窗口数据 -func (dp *DataProcessor) processDirectData(data interface{}) { - // 直接将数据作为map处理 - dataMap, ok := data.(map[string]interface{}) - if !ok { - logger.Error("Unsupported data type: %T", data) - atomic.AddInt64(&dp.stream.droppedCount, 1) - return - } +// 参数: +// - data: 要处理的数据,必须是map[string]interface{}类型 +func (dp *DataProcessor) processDirectData(data map[string]interface{}) { + // 直接使用传入的map数据 + dataMap := data // 创建结果map,预分配合适容量 estimatedSize := len(dp.stream.config.FieldExpressions) + len(dp.stream.config.SimpleFields) @@ -464,7 +439,7 @@ func (dp *DataProcessor) processDirectData(data interface{}) { // 使用预编译的字段信息处理SimpleFields if len(dp.stream.config.SimpleFields) > 0 { for _, fieldSpec := range dp.stream.config.SimpleFields { - dp.stream.processSimpleField(fieldSpec, dataMap, data, result) + dp.stream.processSimpleField(fieldSpec, dataMap, dataMap, result) } } else if len(dp.stream.config.FieldExpressions) == 0 { // 如果没有指定字段且没有表达式字段,保留所有字段 diff --git a/stream/stream.go b/stream/stream.go index 1830bc9..733d4e3 100644 --- a/stream/stream.go +++ b/stream/stream.go @@ -78,13 +78,13 @@ const ( ) type Stream struct { - dataChan chan interface{} + dataChan chan map[string]interface{} filter condition.Condition Window window.Window aggregator aggregator.Aggregator config types.Config - sinks []func(interface{}) - resultChan chan interface{} // 结果通道 + sinks []func([]map[string]interface{}) + resultChan chan []map[string]interface{} // 结果通道 seenResults *sync.Map done chan struct{} // 用于关闭处理协程 sinkWorkerPool chan func() // Sink工作池,避免阻塞 @@ -110,7 +110,7 @@ type Stream struct { persistenceManager *PersistenceManager // 持久化管理器 // 预编译的AddData函数指针,避免每次switch判断 - addDataFunc func(interface{}) // 根据策略预设的函数指针 + addDataFunc func(map[string]interface{}) // 根据策略预设的函数指针 // 预编译字段处理信息,避免重复解析 compiledFieldInfo map[string]*fieldProcessInfo // 字段处理信息缓存 @@ -222,7 +222,10 @@ func (s *Stream) Start() { go processor.Process() } -func (s *Stream) Emit(data interface{}) { +// Emit 添加数据到流处理管道 +// 参数: +// - data: 要处理的数据,必须是map[string]interface{}类型 +func (s *Stream) Emit(data map[string]interface{}) { atomic.AddInt64(&s.inputCount, 1) // 直接调用预编译的函数指针,避免switch判断 s.addDataFunc(data) @@ -304,7 +307,12 @@ func (s *Stream) IsAggregationQuery() bool { // ProcessSync 同步处理单条数据,立即返回结果 // 仅适用于非聚合查询,聚合查询会返回错误 -func (s *Stream) ProcessSync(data interface{}) (interface{}, error) { +// 参数: +// - data: 要处理的数据,必须是map[string]interface{}类型 +// 返回值: +// - map[string]interface{}: 处理后的结果数据,如果不匹配过滤条件返回nil +// - error: 处理错误,如果是聚合查询会返回错误 +func (s *Stream) ProcessSync(data map[string]interface{}) (map[string]interface{}, error) { // 检查是否为聚合查询 if s.config.NeedWindow { return nil, fmt.Errorf("Synchronous processing is not supported for aggregation queries.") @@ -320,12 +328,14 @@ func (s *Stream) ProcessSync(data interface{}) (interface{}, error) { } // processDirectDataSync 同步版本的直接数据处理 -func (s *Stream) processDirectDataSync(data interface{}) (interface{}, error) { - dataMap, ok := data.(map[string]interface{}) - if !ok { - atomic.AddInt64(&s.droppedCount, 1) - return nil, fmt.Errorf("Unsupported data type:%T", data) - } +// 参数: +// - data: 要处理的数据,必须是map[string]interface{}类型 +// 返回值: +// - map[string]interface{}: 处理后的结果数据 +// - error: 处理错误 +func (s *Stream) processDirectDataSync(data map[string]interface{}) (map[string]interface{}, error) { + // 直接使用传入的map,无需类型转换 + dataMap := data // 创建结果map,预分配合适容量 estimatedSize := len(s.config.FieldExpressions) + len(s.config.SimpleFields) @@ -342,7 +352,7 @@ func (s *Stream) processDirectDataSync(data interface{}) (interface{}, error) { // 使用预编译的字段信息处理SimpleFields if len(s.config.SimpleFields) > 0 { for _, fieldSpec := range s.config.SimpleFields { - s.processSimpleField(fieldSpec, dataMap, data, result) + s.processSimpleField(fieldSpec, dataMap, dataMap, result) } } else if len(s.config.FieldExpressions) == 0 { // 如果没有指定字段且没有表达式字段,保留所有字段 diff --git a/stream/stream_factory.go b/stream/stream_factory.go index af0be50..ea9bf25 100644 --- a/stream/stream_factory.go +++ b/stream/stream_factory.go @@ -100,10 +100,10 @@ func (sf *StreamFactory) createWindow(config types.Config) (window.Window, error func (sf *StreamFactory) createStreamInstance(config types.Config, win window.Window) *Stream { perfConfig := config.PerformanceConfig return &Stream{ - dataChan: make(chan interface{}, perfConfig.BufferConfig.DataChannelSize), + dataChan: make(chan map[string]interface{}, perfConfig.BufferConfig.DataChannelSize), config: config, Window: win, - resultChan: make(chan interface{}, perfConfig.BufferConfig.ResultChannelSize), + resultChan: make(chan []map[string]interface{}, perfConfig.BufferConfig.ResultChannelSize), seenResults: &sync.Map{}, done: make(chan struct{}), sinkWorkerPool: make(chan func(), perfConfig.WorkerConfig.SinkPoolSize), diff --git a/stream/stream_field_test.go b/stream/stream_field_test.go index 250d197..e4e4475 100644 --- a/stream/stream_field_test.go +++ b/stream/stream_field_test.go @@ -1,332 +1,332 @@ -package stream - -import ( - "sync" - "testing" - "time" - - "github.com/rulego/streamsql/types" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -// TestSelectStarWithExpressionFields 测试SELECT *与表达式字段的组合 -func TestSelectStarWithExpressionFields(t *testing.T) { - tests := []struct { - name string - simpleFields []string - fieldExpressions map[string]types.FieldExpression - testData map[string]interface{} - expectedFields map[string]interface{} - }{ - { - name: "SELECT * with additional expressions", - simpleFields: []string{"*"}, - fieldExpressions: map[string]types.FieldExpression{ - "name": { - Expression: "UPPER(name)", - Fields: []string{"name"}, - }, - "full_info": { - Expression: "CONCAT(name, ' - ', status)", - Fields: []string{"name", "status"}, - }, - }, - testData: map[string]interface{}{ - "name": "john", - "status": "active", - "age": 25, - }, - expectedFields: map[string]interface{}{ - "name": "JOHN", - "full_info": "john - active", - "status": "active", - "age": 25, - }, - }, - { - name: "SELECT * with field override", - simpleFields: []string{"*"}, - fieldExpressions: map[string]types.FieldExpression{ - "name": { - Expression: "UPPER(name)", - Fields: []string{"name"}, - }, - "age": { - Expression: "age * 2", - Fields: []string{"age"}, - }, - }, - testData: map[string]interface{}{ - "name": "alice", - "age": 30, - "status": "active", - }, - expectedFields: map[string]interface{}{ - "name": "ALICE", - "age": 60.0, // 表达式结果 - "status": "active", - }, - }, - { - name: "SELECT * without expressions", - simpleFields: []string{"*"}, - fieldExpressions: nil, - testData: map[string]interface{}{ - "name": "bob", - "age": 35, - "status": "inactive", - }, - expectedFields: map[string]interface{}{ - "name": "bob", - "age": 35, - "status": "inactive", - }, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - config := types.Config{ - NeedWindow: false, - SimpleFields: tt.simpleFields, - FieldExpressions: tt.fieldExpressions, - } - - stream, err := NewStream(config) - require.NoError(t, err) - defer stream.Stop() - - // 收集结果 - var mu sync.Mutex - var results []interface{} - stream.AddSink(func(result interface{}) { - mu.Lock() - defer mu.Unlock() - results = append(results, result) - }) - - stream.Start() - stream.Emit(tt.testData) - - // 等待处理完成 - time.Sleep(100 * time.Millisecond) - - // 验证结果 - mu.Lock() - defer mu.Unlock() - - require.Len(t, results, 1) - resultData := results[0].([]map[string]interface{})[0] - - for field, expected := range tt.expectedFields { - actual, exists := resultData[field] - assert.True(t, exists, "Field %s should exist", field) - if expected != nil { - // 处理数值类型的比较 - if expectedFloat, ok := expected.(float64); ok { - if actualFloat, ok := actual.(float64); ok { - assert.InEpsilon(t, expectedFloat, actualFloat, 0.0001) - } else if actualInt, ok := actual.(int); ok { - assert.InEpsilon(t, expectedFloat, float64(actualInt), 0.0001) - } else { - t.Errorf("Expected %s to be numeric, got %T", field, actual) - } - } else { - assert.Equal(t, expected, actual, "Field %s mismatch", field) - } - } - } - }) - } -} - -// TestFieldProcessor 测试字段处理器 -func TestFieldProcessor(t *testing.T) { - tests := []struct { - name string - simpleFields []string - testData map[string]interface{} - expected map[string]interface{} - }{ - { - name: "Specific fields", - simpleFields: []string{"name", "age"}, - testData: map[string]interface{}{ - "name": "test", - "age": 25, - "status": "active", - }, - expected: map[string]interface{}{ - "name": "test", - "age": 25, - }, - }, - { - name: "All fields with *", - simpleFields: []string{"*"}, - testData: map[string]interface{}{ - "name": "test", - "age": 25, - "status": "active", - }, - expected: map[string]interface{}{ - "name": "test", - "age": 25, - "status": "active", - }, - }, - { - name: "Mixed fields", - simpleFields: []string{"name", "*"}, - testData: map[string]interface{}{ - "name": "test", - "age": 25, - "status": "active", - }, - expected: map[string]interface{}{ - "name": "test", - "age": 25, - "status": "active", - }, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - config := types.Config{ - NeedWindow: false, - SimpleFields: tt.simpleFields, - } - - stream, err := NewStream(config) - require.NoError(t, err) - defer stream.Stop() - - var mu sync.Mutex - var results []interface{} - stream.AddSink(func(result interface{}) { - mu.Lock() - defer mu.Unlock() - results = append(results, result) - }) - - stream.Start() - stream.Emit(tt.testData) - - time.Sleep(100 * time.Millisecond) - - mu.Lock() - defer mu.Unlock() - - require.Len(t, results, 1) - resultData := results[0].([]map[string]interface{})[0] - - // 验证期望的字段都存在 - for field, expected := range tt.expected { - actual, exists := resultData[field] - assert.True(t, exists, "Field %s should exist", field) - assert.Equal(t, expected, actual, "Field %s value mismatch", field) - } - - // 如果不是 "*",验证没有额外的字段 - if len(tt.simpleFields) == 1 && tt.simpleFields[0] != "*" { - assert.Len(t, resultData, len(tt.expected), "Should only have expected fields") - } - }) - } -} - -// TestExpressionEvaluation 测试表达式计算 -func TestExpressionEvaluation(t *testing.T) { - tests := []struct { - name string - expression types.FieldExpression - testData map[string]interface{} - expected interface{} - }{ - { - name: "String concatenation", - expression: types.FieldExpression{ - Expression: "CONCAT(first_name, ' ', last_name)", - Fields: []string{"first_name", "last_name"}, - }, - testData: map[string]interface{}{ - "first_name": "John", - "last_name": "Doe", - }, - expected: "John Doe", - }, - { - name: "Arithmetic operation", - expression: types.FieldExpression{ - Expression: "price * quantity", - Fields: []string{"price", "quantity"}, - }, - testData: map[string]interface{}{ - "price": 10.5, - "quantity": 3, - }, - expected: 31.5, - }, - { - name: "String transformation", - expression: types.FieldExpression{ - Expression: "UPPER(name)", - Fields: []string{"name"}, - }, - testData: map[string]interface{}{ - "name": "alice", - }, - expected: "ALICE", - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - config := types.Config{ - NeedWindow: false, - FieldExpressions: map[string]types.FieldExpression{ - "result": tt.expression, - }, - } - - stream, err := NewStream(config) - require.NoError(t, err) - defer stream.Stop() - - var mu sync.Mutex - var results []interface{} - stream.AddSink(func(result interface{}) { - mu.Lock() - defer mu.Unlock() - results = append(results, result) - }) - - stream.Start() - stream.Emit(tt.testData) - - time.Sleep(100 * time.Millisecond) - - mu.Lock() - defer mu.Unlock() - - require.Len(t, results, 1) - resultData := results[0].([]map[string]interface{})[0] - - actual, exists := resultData["result"] - assert.True(t, exists, "Result field should exist") - - // 处理数值类型的比较 - if expectedFloat, ok := tt.expected.(float64); ok { - if actualFloat, ok := actual.(float64); ok { - assert.InEpsilon(t, expectedFloat, actualFloat, 0.0001) - } else { - t.Errorf("Expected float64, got %T", actual) - } - } else { - assert.Equal(t, tt.expected, actual) - } - }) - } -} \ No newline at end of file +package stream + +import ( + "sync" + "testing" + "time" + + "github.com/rulego/streamsql/types" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// TestSelectStarWithExpressionFields 测试SELECT *与表达式字段的组合 +func TestSelectStarWithExpressionFields(t *testing.T) { + tests := []struct { + name string + simpleFields []string + fieldExpressions map[string]types.FieldExpression + testData map[string]interface{} + expectedFields map[string]interface{} + }{ + { + name: "SELECT * with additional expressions", + simpleFields: []string{"*"}, + fieldExpressions: map[string]types.FieldExpression{ + "name": { + Expression: "UPPER(name)", + Fields: []string{"name"}, + }, + "full_info": { + Expression: "CONCAT(name, ' - ', status)", + Fields: []string{"name", "status"}, + }, + }, + testData: map[string]interface{}{ + "name": "john", + "status": "active", + "age": 25, + }, + expectedFields: map[string]interface{}{ + "name": "JOHN", + "full_info": "john - active", + "status": "active", + "age": 25, + }, + }, + { + name: "SELECT * with field override", + simpleFields: []string{"*"}, + fieldExpressions: map[string]types.FieldExpression{ + "name": { + Expression: "UPPER(name)", + Fields: []string{"name"}, + }, + "age": { + Expression: "age * 2", + Fields: []string{"age"}, + }, + }, + testData: map[string]interface{}{ + "name": "alice", + "age": 30, + "status": "active", + }, + expectedFields: map[string]interface{}{ + "name": "ALICE", + "age": 60.0, // 表达式结果 + "status": "active", + }, + }, + { + name: "SELECT * without expressions", + simpleFields: []string{"*"}, + fieldExpressions: nil, + testData: map[string]interface{}{ + "name": "bob", + "age": 35, + "status": "inactive", + }, + expectedFields: map[string]interface{}{ + "name": "bob", + "age": 35, + "status": "inactive", + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + config := types.Config{ + NeedWindow: false, + SimpleFields: tt.simpleFields, + FieldExpressions: tt.fieldExpressions, + } + + stream, err := NewStream(config) + require.NoError(t, err) + defer stream.Stop() + + // 收集结果 + var mu sync.Mutex + var results []interface{} + stream.AddSink(func(result []map[string]interface{}) { + mu.Lock() + defer mu.Unlock() + results = append(results, result) + }) + + stream.Start() + stream.Emit(tt.testData) + + // 等待处理完成 + time.Sleep(100 * time.Millisecond) + + // 验证结果 + mu.Lock() + defer mu.Unlock() + + require.Len(t, results, 1) + resultData := results[0].([]map[string]interface{})[0] + + for field, expected := range tt.expectedFields { + actual, exists := resultData[field] + assert.True(t, exists, "Field %s should exist", field) + if expected != nil { + // 处理数值类型的比较 + if expectedFloat, ok := expected.(float64); ok { + if actualFloat, ok := actual.(float64); ok { + assert.InEpsilon(t, expectedFloat, actualFloat, 0.0001) + } else if actualInt, ok := actual.(int); ok { + assert.InEpsilon(t, expectedFloat, float64(actualInt), 0.0001) + } else { + t.Errorf("Expected %s to be numeric, got %T", field, actual) + } + } else { + assert.Equal(t, expected, actual, "Field %s mismatch", field) + } + } + } + }) + } +} + +// TestFieldProcessor 测试字段处理器 +func TestFieldProcessor(t *testing.T) { + tests := []struct { + name string + simpleFields []string + testData map[string]interface{} + expected map[string]interface{} + }{ + { + name: "Specific fields", + simpleFields: []string{"name", "age"}, + testData: map[string]interface{}{ + "name": "test", + "age": 25, + "status": "active", + }, + expected: map[string]interface{}{ + "name": "test", + "age": 25, + }, + }, + { + name: "All fields with *", + simpleFields: []string{"*"}, + testData: map[string]interface{}{ + "name": "test", + "age": 25, + "status": "active", + }, + expected: map[string]interface{}{ + "name": "test", + "age": 25, + "status": "active", + }, + }, + { + name: "Mixed fields", + simpleFields: []string{"name", "*"}, + testData: map[string]interface{}{ + "name": "test", + "age": 25, + "status": "active", + }, + expected: map[string]interface{}{ + "name": "test", + "age": 25, + "status": "active", + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + config := types.Config{ + NeedWindow: false, + SimpleFields: tt.simpleFields, + } + + stream, err := NewStream(config) + require.NoError(t, err) + defer stream.Stop() + + var mu sync.Mutex + var results []interface{} + stream.AddSink(func(result []map[string]interface{}) { + mu.Lock() + defer mu.Unlock() + results = append(results, result) + }) + + stream.Start() + stream.Emit(tt.testData) + + time.Sleep(100 * time.Millisecond) + + mu.Lock() + defer mu.Unlock() + + require.Len(t, results, 1) + resultData := results[0].([]map[string]interface{})[0] + + // 验证期望的字段都存在 + for field, expected := range tt.expected { + actual, exists := resultData[field] + assert.True(t, exists, "Field %s should exist", field) + assert.Equal(t, expected, actual, "Field %s value mismatch", field) + } + + // 如果不是 "*",验证没有额外的字段 + if len(tt.simpleFields) == 1 && tt.simpleFields[0] != "*" { + assert.Len(t, resultData, len(tt.expected), "Should only have expected fields") + } + }) + } +} + +// TestExpressionEvaluation 测试表达式计算 +func TestExpressionEvaluation(t *testing.T) { + tests := []struct { + name string + expression types.FieldExpression + testData map[string]interface{} + expected interface{} + }{ + { + name: "String concatenation", + expression: types.FieldExpression{ + Expression: "CONCAT(first_name, ' ', last_name)", + Fields: []string{"first_name", "last_name"}, + }, + testData: map[string]interface{}{ + "first_name": "John", + "last_name": "Doe", + }, + expected: "John Doe", + }, + { + name: "Arithmetic operation", + expression: types.FieldExpression{ + Expression: "price * quantity", + Fields: []string{"price", "quantity"}, + }, + testData: map[string]interface{}{ + "price": 10.5, + "quantity": 3, + }, + expected: 31.5, + }, + { + name: "String transformation", + expression: types.FieldExpression{ + Expression: "UPPER(name)", + Fields: []string{"name"}, + }, + testData: map[string]interface{}{ + "name": "alice", + }, + expected: "ALICE", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + config := types.Config{ + NeedWindow: false, + FieldExpressions: map[string]types.FieldExpression{ + "result": tt.expression, + }, + } + + stream, err := NewStream(config) + require.NoError(t, err) + defer stream.Stop() + + var mu sync.Mutex + var results []interface{} + stream.AddSink(func(result []map[string]interface{}) { + mu.Lock() + defer mu.Unlock() + results = append(results, result) + }) + + stream.Start() + stream.Emit(tt.testData) + + time.Sleep(100 * time.Millisecond) + + mu.Lock() + defer mu.Unlock() + + require.Len(t, results, 1) + resultData := results[0].([]map[string]interface{})[0] + + actual, exists := resultData["result"] + assert.True(t, exists, "Result field should exist") + + // 处理数值类型的比较 + if expectedFloat, ok := tt.expected.(float64); ok { + if actualFloat, ok := actual.(float64); ok { + assert.InEpsilon(t, expectedFloat, actualFloat, 0.0001) + } else { + t.Errorf("Expected float64, got %T", actual) + } + } else { + assert.Equal(t, tt.expected, actual) + } + }) + } +} diff --git a/stream/stream_performance_test.go b/stream/stream_performance_test.go index ccaa124..138273e 100644 --- a/stream/stream_performance_test.go +++ b/stream/stream_performance_test.go @@ -1,442 +1,442 @@ -package stream - -import ( - "sync" - "testing" - "time" - - "github.com/rulego/streamsql/aggregator" - "github.com/rulego/streamsql/types" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -// TestStreamUnifiedConfigIntegration 测试Stream和Window统一配置的集成 -func TestStreamUnifiedConfigIntegration(t *testing.T) { - testCases := []struct { - name string - performanceConfig types.PerformanceConfig - expectedWindowBufferSize int - }{ - { - name: "默认配置", - performanceConfig: types.DefaultPerformanceConfig(), - expectedWindowBufferSize: 1000, - }, - { - name: "高性能配置", - performanceConfig: types.HighPerformanceConfig(), - expectedWindowBufferSize: 5000, - }, - { - name: "低延迟配置", - performanceConfig: types.LowLatencyConfig(), - expectedWindowBufferSize: 100, - }, - { - name: "零数据丢失配置", - performanceConfig: types.ZeroDataLossConfig(), - expectedWindowBufferSize: 2000, - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - config := types.Config{ - NeedWindow: true, - WindowConfig: types.WindowConfig{ - Type: "tumbling", - Params: map[string]interface{}{ - "size": "5s", - }, - }, - SelectFields: map[string]aggregator.AggregateType{ - "value": aggregator.Count, - }, - PerformanceConfig: tc.performanceConfig, - } - - s, err := NewStream(config) - require.NoError(t, err) - defer s.Stop() - - // 验证stream的缓冲区配置 - assert.Equal(t, tc.performanceConfig.BufferConfig.DataChannelSize, cap(s.dataChan), - "数据通道大小不匹配") - assert.Equal(t, tc.performanceConfig.BufferConfig.ResultChannelSize, cap(s.resultChan), - "结果通道大小不匹配") - - // 验证窗口创建成功 - assert.NotNil(t, s.Window, "窗口应该被创建") - t.Logf("窗口已创建,类型: %T", s.Window) - }) - } -} - -// TestStreamUnifiedConfigPerformanceImpact 测试统一配置对Stream性能的影响 -func TestStreamUnifiedConfigPerformanceImpact(t *testing.T) { - configs := map[string]types.PerformanceConfig{ - "默认配置": types.DefaultPerformanceConfig(), - "高性能配置": types.HighPerformanceConfig(), - "低延迟配置": types.LowLatencyConfig(), - } - - for name, perfConfig := range configs { - t.Run(name, func(t *testing.T) { - config := types.Config{ - NeedWindow: true, - WindowConfig: types.WindowConfig{ - Type: "tumbling", - Params: map[string]interface{}{ - "size": "1s", - }, - }, - SelectFields: map[string]aggregator.AggregateType{ - "value": aggregator.Sum, - }, - PerformanceConfig: perfConfig, - } - - s, err := NewStream(config) - require.NoError(t, err) - defer s.Stop() - - go s.Start() - - // 发送测试数据并测量性能 - dataCount := 1000 - startTime := time.Now() - - for i := 0; i < dataCount; i++ { - data := map[string]interface{}{ - "value": i, - "timestamp": time.Now().Unix(), - } - - select { - case s.dataChan <- data: - // 成功发送 - case <-time.After(100 * time.Millisecond): - // 发送超时 - t.Logf("第%d条数据发送超时", i) - break - } - } - - processingTime := time.Since(startTime) - t.Logf("%s 处理%d条数据耗时: %v", name, dataCount, processingTime) - - // 等待一些结果 - time.Sleep(1500 * time.Millisecond) - - // 检查结果 - resultCount := 0 - for { - select { - case <-s.resultChan: - resultCount++ - default: - goto done - } - } - done: - // 性能测试主要关注处理时间,结果数量可能因窗口触发时机而变化 - }) - } -} - -// TestStreamUnifiedConfigErrorHandling 测试统一配置的错误处理 -func TestStreamUnifiedConfigErrorHandling(t *testing.T) { - tests := []struct { - name string - config types.Config - expectError bool - description string - }{ - { - name: "无效窗口类型", - config: types.Config{ - NeedWindow: true, - WindowConfig: types.WindowConfig{ - Type: "invalid_window_type", - Params: map[string]interface{}{ - "size": "5s", - }, - }, - SelectFields: map[string]aggregator.AggregateType{ - "value": aggregator.Count, - }, - PerformanceConfig: types.DefaultPerformanceConfig(), - }, - expectError: true, - description: "无效的窗口类型应该导致创建失败", - }, - { - name: "缺少窗口大小参数", - config: types.Config{ - NeedWindow: true, - WindowConfig: types.WindowConfig{ - Type: "tumbling", - Params: map[string]interface{}{}, - }, - SelectFields: map[string]aggregator.AggregateType{ - "value": aggregator.Count, - }, - PerformanceConfig: types.DefaultPerformanceConfig(), - }, - expectError: true, - description: "缺少size参数应该导致创建失败", - }, - { - name: "有效配置", - config: types.Config{ - NeedWindow: true, - WindowConfig: types.WindowConfig{ - Type: "tumbling", - Params: map[string]interface{}{ - "size": "5s", - }, - }, - SelectFields: map[string]aggregator.AggregateType{ - "value": aggregator.Count, - }, - PerformanceConfig: types.DefaultPerformanceConfig(), - }, - expectError: false, - description: "有效配置应该创建成功", - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - stream, err := NewStream(tt.config) - if tt.expectError { - assert.Error(t, err, tt.description) - assert.Nil(t, stream) - } else { - assert.NoError(t, err, tt.description) - assert.NotNil(t, stream) - if stream != nil { - defer stream.Stop() - } - } - }) - } -} - -// TestStreamUnifiedConfigCompatibility 测试统一配置的兼容性 -func TestStreamUnifiedConfigCompatibility(t *testing.T) { - // 测试新的统一配置 - newConfig := types.Config{ - NeedWindow: false, - SelectFields: map[string]aggregator.AggregateType{ - "value": aggregator.Count, - }, - PerformanceConfig: types.HighPerformanceConfig(), - } - - s1, err := NewStream(newConfig) - require.NoError(t, err) - defer s1.Stop() - - // 验证新配置生效 - expectedDataSize := types.HighPerformanceConfig().BufferConfig.DataChannelSize - assert.Equal(t, expectedDataSize, cap(s1.dataChan), "高性能配置的数据通道大小不匹配") - - // 测试默认配置 - defaultConfig := types.Config{ - NeedWindow: false, - SelectFields: map[string]aggregator.AggregateType{ - "value": aggregator.Count, - }, - PerformanceConfig: types.DefaultPerformanceConfig(), - } - - s2, err := NewStream(defaultConfig) - require.NoError(t, err) - defer s2.Stop() - - // 验证默认配置 - expectedDefaultSize := types.DefaultPerformanceConfig().BufferConfig.DataChannelSize - assert.Equal(t, expectedDefaultSize, cap(s2.dataChan), "默认配置的数据通道大小不匹配") - - t.Logf("高性能配置数据通道大小: %d", cap(s1.dataChan)) - t.Logf("默认配置数据通道大小: %d", cap(s2.dataChan)) -} - -// TestStatsManager 测试统计管理器 -func TestStatsManager(t *testing.T) { - config := types.Config{ - NeedWindow: false, - SimpleFields: []string{"value"}, - } - - stream, err := NewStream(config) - require.NoError(t, err) - defer stream.Stop() - - // 启动流处理 - stream.Start() - - // 发送一些数据来生成统计信息 - for i := 0; i < 10; i++ { - stream.Emit(map[string]interface{}{"value": i}) - } - - // 等待处理完成 - time.Sleep(100 * time.Millisecond) - - // 测试基本统计 - stats := stream.GetStats() - assert.Equal(t, int64(10), stats[InputCount], "输入计数不匹配") - assert.GreaterOrEqual(t, stats[OutputCount], int64(1), "输出计数应该大于等于1") - - // 测试重置统计 - stream.ResetStats() - stats = stream.GetStats() - assert.Equal(t, int64(0), stats[InputCount], "重置后输入计数应该为0") -} - -// TestDataHandler 测试数据处理器 -func TestDataHandler(t *testing.T) { - tests := []struct { - name string - performanceConfig types.PerformanceConfig - dataCount int - expectedDrops bool - }{ - { - name: "高性能配置 - 无丢弃", - performanceConfig: types.HighPerformanceConfig(), - dataCount: 100, - expectedDrops: false, - }, - { - name: "低延迟配置 - 可能丢弃", - performanceConfig: types.LowLatencyConfig(), - dataCount: 1000, - expectedDrops: true, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - config := types.Config{ - NeedWindow: false, - SimpleFields: []string{"value"}, - PerformanceConfig: tt.performanceConfig, - } - - stream, err := NewStream(config) - require.NoError(t, err) - defer stream.Stop() - - stream.Start() - - // 快速发送大量数据 - for i := 0; i < tt.dataCount; i++ { - stream.Emit(map[string]interface{}{"value": i}) - } - - time.Sleep(100 * time.Millisecond) - - stats := stream.GetStats() - droppedCount := stats[DroppedCount] - - if tt.expectedDrops { - // 在高负载下可能会有丢弃 - t.Logf("%s: 输入 %d, 丢弃 %d", tt.name, stats[InputCount], droppedCount) - } else { - // 高性能配置应该能处理所有数据 - assert.Equal(t, int64(0), droppedCount, "高性能配置不应该丢弃数据") - } - }) - } -} - -// TestResultHandler 测试结果处理器 -func TestResultHandler(t *testing.T) { - config := types.Config{ - NeedWindow: false, - SimpleFields: []string{"value"}, - } - - stream, err := NewStream(config) - require.NoError(t, err) - defer stream.Stop() - - // 测试Sink功能 - var mu sync.Mutex - var receivedResults []interface{} - - stream.AddSink(func(result interface{}) { - mu.Lock() - defer mu.Unlock() - receivedResults = append(receivedResults, result) - }) - - stream.Start() - - // 发送测试数据 - testData := []map[string]interface{}{ - {"value": 1}, - {"value": 2}, - {"value": 3}, - } - - for _, data := range testData { - stream.Emit(data) - } - - time.Sleep(100 * time.Millisecond) - - // 验证结果 - mu.Lock() - defer mu.Unlock() - - assert.GreaterOrEqual(t, len(receivedResults), len(testData), "应该接收到所有结果") - - // 验证结果格式 - for _, result := range receivedResults { - assert.IsType(t, []map[string]interface{}{}, result, "结果应该是map切片类型") - resultSlice := result.([]map[string]interface{}) - assert.Greater(t, len(resultSlice), 0, "结果切片不应该为空") - } -} - -// TestPerformanceConfigurations 测试不同性能配置的效果 -func TestPerformanceConfigurations(t *testing.T) { - configs := map[string]types.PerformanceConfig{ - "Default": types.DefaultPerformanceConfig(), - "HighPerformance": types.HighPerformanceConfig(), - "LowLatency": types.LowLatencyConfig(), - "ZeroDataLoss": types.ZeroDataLossConfig(), - } - - for name, perfConfig := range configs { - t.Run(name, func(t *testing.T) { - config := types.Config{ - NeedWindow: false, - SimpleFields: []string{"value"}, - PerformanceConfig: perfConfig, - } - - stream, err := NewStream(config) - require.NoError(t, err) - defer stream.Stop() - - // 验证缓冲区大小 - assert.Equal(t, perfConfig.BufferConfig.DataChannelSize, cap(stream.dataChan)) - assert.Equal(t, perfConfig.BufferConfig.ResultChannelSize, cap(stream.resultChan)) - - // 验证工作池配置 - assert.Equal(t, perfConfig.WorkerConfig.SinkPoolSize, cap(stream.sinkWorkerPool)) - - t.Logf("%s配置: 数据通道=%d, 结果通道=%d, Sink池=%d", - name, - cap(stream.dataChan), - cap(stream.resultChan), - cap(stream.sinkWorkerPool)) - }) - } -} \ No newline at end of file +package stream + +import ( + "sync" + "testing" + "time" + + "github.com/rulego/streamsql/aggregator" + "github.com/rulego/streamsql/types" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// TestStreamUnifiedConfigIntegration 测试Stream和Window统一配置的集成 +func TestStreamUnifiedConfigIntegration(t *testing.T) { + testCases := []struct { + name string + performanceConfig types.PerformanceConfig + expectedWindowBufferSize int + }{ + { + name: "默认配置", + performanceConfig: types.DefaultPerformanceConfig(), + expectedWindowBufferSize: 1000, + }, + { + name: "高性能配置", + performanceConfig: types.HighPerformanceConfig(), + expectedWindowBufferSize: 5000, + }, + { + name: "低延迟配置", + performanceConfig: types.LowLatencyConfig(), + expectedWindowBufferSize: 100, + }, + { + name: "零数据丢失配置", + performanceConfig: types.ZeroDataLossConfig(), + expectedWindowBufferSize: 2000, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + config := types.Config{ + NeedWindow: true, + WindowConfig: types.WindowConfig{ + Type: "tumbling", + Params: map[string]interface{}{ + "size": "5s", + }, + }, + SelectFields: map[string]aggregator.AggregateType{ + "value": aggregator.Count, + }, + PerformanceConfig: tc.performanceConfig, + } + + s, err := NewStream(config) + require.NoError(t, err) + defer s.Stop() + + // 验证stream的缓冲区配置 + assert.Equal(t, tc.performanceConfig.BufferConfig.DataChannelSize, cap(s.dataChan), + "数据通道大小不匹配") + assert.Equal(t, tc.performanceConfig.BufferConfig.ResultChannelSize, cap(s.resultChan), + "结果通道大小不匹配") + + // 验证窗口创建成功 + assert.NotNil(t, s.Window, "窗口应该被创建") + t.Logf("窗口已创建,类型: %T", s.Window) + }) + } +} + +// TestStreamUnifiedConfigPerformanceImpact 测试统一配置对Stream性能的影响 +func TestStreamUnifiedConfigPerformanceImpact(t *testing.T) { + configs := map[string]types.PerformanceConfig{ + "默认配置": types.DefaultPerformanceConfig(), + "高性能配置": types.HighPerformanceConfig(), + "低延迟配置": types.LowLatencyConfig(), + } + + for name, perfConfig := range configs { + t.Run(name, func(t *testing.T) { + config := types.Config{ + NeedWindow: true, + WindowConfig: types.WindowConfig{ + Type: "tumbling", + Params: map[string]interface{}{ + "size": "1s", + }, + }, + SelectFields: map[string]aggregator.AggregateType{ + "value": aggregator.Sum, + }, + PerformanceConfig: perfConfig, + } + + s, err := NewStream(config) + require.NoError(t, err) + defer s.Stop() + + go s.Start() + + // 发送测试数据并测量性能 + dataCount := 1000 + startTime := time.Now() + + for i := 0; i < dataCount; i++ { + data := map[string]interface{}{ + "value": i, + "timestamp": time.Now().Unix(), + } + + select { + case s.dataChan <- data: + // 成功发送 + case <-time.After(100 * time.Millisecond): + // 发送超时 + t.Logf("第%d条数据发送超时", i) + break + } + } + + processingTime := time.Since(startTime) + t.Logf("%s 处理%d条数据耗时: %v", name, dataCount, processingTime) + + // 等待一些结果 + time.Sleep(1500 * time.Millisecond) + + // 检查结果 + resultCount := 0 + for { + select { + case <-s.resultChan: + resultCount++ + default: + goto done + } + } + done: + // 性能测试主要关注处理时间,结果数量可能因窗口触发时机而变化 + }) + } +} + +// TestStreamUnifiedConfigErrorHandling 测试统一配置的错误处理 +func TestStreamUnifiedConfigErrorHandling(t *testing.T) { + tests := []struct { + name string + config types.Config + expectError bool + description string + }{ + { + name: "无效窗口类型", + config: types.Config{ + NeedWindow: true, + WindowConfig: types.WindowConfig{ + Type: "invalid_window_type", + Params: map[string]interface{}{ + "size": "5s", + }, + }, + SelectFields: map[string]aggregator.AggregateType{ + "value": aggregator.Count, + }, + PerformanceConfig: types.DefaultPerformanceConfig(), + }, + expectError: true, + description: "无效的窗口类型应该导致创建失败", + }, + { + name: "缺少窗口大小参数", + config: types.Config{ + NeedWindow: true, + WindowConfig: types.WindowConfig{ + Type: "tumbling", + Params: map[string]interface{}{}, + }, + SelectFields: map[string]aggregator.AggregateType{ + "value": aggregator.Count, + }, + PerformanceConfig: types.DefaultPerformanceConfig(), + }, + expectError: true, + description: "缺少size参数应该导致创建失败", + }, + { + name: "有效配置", + config: types.Config{ + NeedWindow: true, + WindowConfig: types.WindowConfig{ + Type: "tumbling", + Params: map[string]interface{}{ + "size": "5s", + }, + }, + SelectFields: map[string]aggregator.AggregateType{ + "value": aggregator.Count, + }, + PerformanceConfig: types.DefaultPerformanceConfig(), + }, + expectError: false, + description: "有效配置应该创建成功", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + stream, err := NewStream(tt.config) + if tt.expectError { + assert.Error(t, err, tt.description) + assert.Nil(t, stream) + } else { + assert.NoError(t, err, tt.description) + assert.NotNil(t, stream) + if stream != nil { + defer stream.Stop() + } + } + }) + } +} + +// TestStreamUnifiedConfigCompatibility 测试统一配置的兼容性 +func TestStreamUnifiedConfigCompatibility(t *testing.T) { + // 测试新的统一配置 + newConfig := types.Config{ + NeedWindow: false, + SelectFields: map[string]aggregator.AggregateType{ + "value": aggregator.Count, + }, + PerformanceConfig: types.HighPerformanceConfig(), + } + + s1, err := NewStream(newConfig) + require.NoError(t, err) + defer s1.Stop() + + // 验证新配置生效 + expectedDataSize := types.HighPerformanceConfig().BufferConfig.DataChannelSize + assert.Equal(t, expectedDataSize, cap(s1.dataChan), "高性能配置的数据通道大小不匹配") + + // 测试默认配置 + defaultConfig := types.Config{ + NeedWindow: false, + SelectFields: map[string]aggregator.AggregateType{ + "value": aggregator.Count, + }, + PerformanceConfig: types.DefaultPerformanceConfig(), + } + + s2, err := NewStream(defaultConfig) + require.NoError(t, err) + defer s2.Stop() + + // 验证默认配置 + expectedDefaultSize := types.DefaultPerformanceConfig().BufferConfig.DataChannelSize + assert.Equal(t, expectedDefaultSize, cap(s2.dataChan), "默认配置的数据通道大小不匹配") + + t.Logf("高性能配置数据通道大小: %d", cap(s1.dataChan)) + t.Logf("默认配置数据通道大小: %d", cap(s2.dataChan)) +} + +// TestStatsManager 测试统计管理器 +func TestStatsManager(t *testing.T) { + config := types.Config{ + NeedWindow: false, + SimpleFields: []string{"value"}, + } + + stream, err := NewStream(config) + require.NoError(t, err) + defer stream.Stop() + + // 启动流处理 + stream.Start() + + // 发送一些数据来生成统计信息 + for i := 0; i < 10; i++ { + stream.Emit(map[string]interface{}{"value": i}) + } + + // 等待处理完成 + time.Sleep(100 * time.Millisecond) + + // 测试基本统计 + stats := stream.GetStats() + assert.Equal(t, int64(10), stats[InputCount], "输入计数不匹配") + assert.GreaterOrEqual(t, stats[OutputCount], int64(1), "输出计数应该大于等于1") + + // 测试重置统计 + stream.ResetStats() + stats = stream.GetStats() + assert.Equal(t, int64(0), stats[InputCount], "重置后输入计数应该为0") +} + +// TestDataHandler 测试数据处理器 +func TestDataHandler(t *testing.T) { + tests := []struct { + name string + performanceConfig types.PerformanceConfig + dataCount int + expectedDrops bool + }{ + { + name: "高性能配置 - 无丢弃", + performanceConfig: types.HighPerformanceConfig(), + dataCount: 100, + expectedDrops: false, + }, + { + name: "低延迟配置 - 可能丢弃", + performanceConfig: types.LowLatencyConfig(), + dataCount: 1000, + expectedDrops: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + config := types.Config{ + NeedWindow: false, + SimpleFields: []string{"value"}, + PerformanceConfig: tt.performanceConfig, + } + + stream, err := NewStream(config) + require.NoError(t, err) + defer stream.Stop() + + stream.Start() + + // 快速发送大量数据 + for i := 0; i < tt.dataCount; i++ { + stream.Emit(map[string]interface{}{"value": i}) + } + + time.Sleep(100 * time.Millisecond) + + stats := stream.GetStats() + droppedCount := stats[DroppedCount] + + if tt.expectedDrops { + // 在高负载下可能会有丢弃 + t.Logf("%s: 输入 %d, 丢弃 %d", tt.name, stats[InputCount], droppedCount) + } else { + // 高性能配置应该能处理所有数据 + assert.Equal(t, int64(0), droppedCount, "高性能配置不应该丢弃数据") + } + }) + } +} + +// TestResultHandler 测试结果处理器 +func TestResultHandler(t *testing.T) { + config := types.Config{ + NeedWindow: false, + SimpleFields: []string{"value"}, + } + + stream, err := NewStream(config) + require.NoError(t, err) + defer stream.Stop() + + // 测试Sink功能 + var mu sync.Mutex + var receivedResults []interface{} + + stream.AddSink(func(result []map[string]interface{}) { + mu.Lock() + defer mu.Unlock() + receivedResults = append(receivedResults, result) + }) + + stream.Start() + + // 发送测试数据 + testData := []map[string]interface{}{ + {"value": 1}, + {"value": 2}, + {"value": 3}, + } + + for _, data := range testData { + stream.Emit(data) + } + + time.Sleep(100 * time.Millisecond) + + // 验证结果 + mu.Lock() + defer mu.Unlock() + + assert.GreaterOrEqual(t, len(receivedResults), len(testData), "应该接收到所有结果") + + // 验证结果格式 + for _, result := range receivedResults { + assert.IsType(t, []map[string]interface{}{}, result, "结果应该是map切片类型") + resultSlice := result.([]map[string]interface{}) + assert.Greater(t, len(resultSlice), 0, "结果切片不应该为空") + } +} + +// TestPerformanceConfigurations 测试不同性能配置的效果 +func TestPerformanceConfigurations(t *testing.T) { + configs := map[string]types.PerformanceConfig{ + "Default": types.DefaultPerformanceConfig(), + "HighPerformance": types.HighPerformanceConfig(), + "LowLatency": types.LowLatencyConfig(), + "ZeroDataLoss": types.ZeroDataLossConfig(), + } + + for name, perfConfig := range configs { + t.Run(name, func(t *testing.T) { + config := types.Config{ + NeedWindow: false, + SimpleFields: []string{"value"}, + PerformanceConfig: perfConfig, + } + + stream, err := NewStream(config) + require.NoError(t, err) + defer stream.Stop() + + // 验证缓冲区大小 + assert.Equal(t, perfConfig.BufferConfig.DataChannelSize, cap(stream.dataChan)) + assert.Equal(t, perfConfig.BufferConfig.ResultChannelSize, cap(stream.resultChan)) + + // 验证工作池配置 + assert.Equal(t, perfConfig.WorkerConfig.SinkPoolSize, cap(stream.sinkWorkerPool)) + + t.Logf("%s配置: 数据通道=%d, 结果通道=%d, Sink池=%d", + name, + cap(stream.dataChan), + cap(stream.resultChan), + cap(stream.sinkWorkerPool)) + }) + } +} diff --git a/stream/stream_persistence_test.go b/stream/stream_persistence_test.go index d8f9d40..308c131 100644 --- a/stream/stream_persistence_test.go +++ b/stream/stream_persistence_test.go @@ -25,7 +25,7 @@ func TestPersistenceManagerBasic(t *testing.T) { defer pm.Stop() // 测试数据持久化 - testData := []interface{}{ + testData := []map[string]interface{}{ map[string]interface{}{"id": 1, "value": "test1"}, map[string]interface{}{"id": 2, "value": "test2"}, map[string]interface{}{"id": 3, "value": "test3"}, diff --git a/stream/stream_test.go b/stream/stream_test.go index 6f43efb..0234e8e 100644 --- a/stream/stream_test.go +++ b/stream/stream_test.go @@ -17,7 +17,7 @@ func TestStreamBasicFunctionality(t *testing.T) { name string config types.Config filter string - testData []interface{} + testData []map[string]interface{} expectedDevice string expectedTemp float64 expectedHum float64 @@ -37,7 +37,7 @@ func TestStreamBasicFunctionality(t *testing.T) { NeedWindow: true, }, filter: "device == 'aa' && temperature > 10", - testData: []interface{}{ + testData: []map[string]interface{}{ map[string]interface{}{"device": "aa", "temperature": 25.0, "humidity": 60}, map[string]interface{}{"device": "aa", "temperature": 30.0, "humidity": 55}, map[string]interface{}{"device": "bb", "temperature": 22.0, "humidity": 70}, @@ -61,7 +61,7 @@ func TestStreamBasicFunctionality(t *testing.T) { NeedWindow: true, }, filter: "device == 'aa'", - testData: []interface{}{ + testData: []map[string]interface{}{ map[string]interface{}{"device": "aa", "temperature": 25.0}, map[string]interface{}{"device": "aa", "humidity": 60}, map[string]interface{}{"device": "aa", "temperature": 30.0}, @@ -87,7 +87,7 @@ func TestStreamBasicFunctionality(t *testing.T) { // 添加 Sink 函数来捕获结果 resultChan := make(chan interface{}, 1) - strm.AddSink(func(result interface{}) { + strm.AddSink(func(result []map[string]interface{}) { select { case resultChan <- result: default: @@ -152,7 +152,7 @@ func TestStreamWithoutFilter(t *testing.T) { strm.Start() - testData := []interface{}{ + testData := []map[string]interface{}{ map[string]interface{}{"device": "aa", "temperature": 25.0, "humidity": 60}, map[string]interface{}{"device": "aa", "temperature": 30.0, "humidity": 55}, map[string]interface{}{"device": "bb", "temperature": 22.0, "humidity": 70}, @@ -164,7 +164,7 @@ func TestStreamWithoutFilter(t *testing.T) { // 捕获结果 resultChan := make(chan interface{}) - strm.AddSink(func(result interface{}) { + strm.AddSink(func(result []map[string]interface{}) { resultChan <- result }) diff --git a/stream/stream_window_test.go b/stream/stream_window_test.go index 85af2b7..4a4e6d0 100644 --- a/stream/stream_window_test.go +++ b/stream/stream_window_test.go @@ -1,244 +1,244 @@ -package stream - -import ( - "fmt" - "testing" - "time" - - "github.com/rulego/streamsql/aggregator" - "github.com/rulego/streamsql/types" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -// TestWindowSlotAggregation 测试窗口时间槽聚合 -func TestWindowSlotAggregation(t *testing.T) { - config := types.Config{ - WindowConfig: types.WindowConfig{ - Type: "sliding", - Params: map[string]interface{}{"size": 2 * time.Second, "slide": 1 * time.Second}, - TsProp: "ts", - }, - GroupFields: []string{"device"}, - SelectFields: map[string]aggregator.AggregateType{ - "temperature": aggregator.Max, - "humidity": aggregator.Min, - "start": aggregator.WindowStart, - "end": aggregator.WindowEnd, - }, - NeedWindow: true, - } - - strm, err := NewStream(config) - require.NoError(t, err) - defer strm.Stop() - - strm.Start() - - // 使用固定时间戳的测试数据 - baseTime := time.Date(2025, 4, 7, 16, 46, 0, 0, time.UTC) - testData := []interface{}{ - map[string]interface{}{"device": "aa", "temperature": 25.0, "humidity": 60, "ts": baseTime}, - map[string]interface{}{"device": "aa", "temperature": 30.0, "humidity": 55, "ts": baseTime.Add(1 * time.Second)}, - map[string]interface{}{"device": "bb", "temperature": 22.0, "humidity": 70, "ts": baseTime}, - } - - for _, data := range testData { - strm.Emit(data) - } - - // 捕获结果 - resultChan := make(chan interface{}) - strm.AddSink(func(result interface{}) { - resultChan <- result - }) - - // 等待窗口触发 - time.Sleep(3 * time.Second) - - select { - case actual := <-resultChan: - expected := []map[string]interface{}{ - { - "device": "aa", - "temperature": 30.0, - "humidity": 55.0, - "start": baseTime.UnixNano(), - "end": baseTime.Add(2 * time.Second).UnixNano(), - }, - { - "device": "bb", - "temperature": 22.0, - "humidity": 70.0, - "start": baseTime.UnixNano(), - "end": baseTime.Add(2 * time.Second).UnixNano(), - }, - } - - assert.IsType(t, []map[string]interface{}{}, actual) - resultSlice := actual.([]map[string]interface{}) - assert.Len(t, resultSlice, 2) - - for _, expectedResult := range expected { - found := false - for _, resultMap := range resultSlice { - if resultMap["device"] == expectedResult["device"] { - assert.InEpsilon(t, expectedResult["temperature"].(float64), resultMap["temperature"].(float64), 0.0001) - assert.InEpsilon(t, expectedResult["humidity"].(float64), resultMap["humidity"].(float64), 0.0001) - assert.Equal(t, expectedResult["start"].(int64), resultMap["start"].(int64)) - assert.Equal(t, expectedResult["end"].(int64), resultMap["end"].(int64)) - found = true - break - } - } - assert.True(t, found, fmt.Sprintf("Expected result for device %v not found", expectedResult["device"])) - } - case <-time.After(10 * time.Second): - t.Fatal("Timeout waiting for results") - } -} - -// TestWindowTypes 测试不同类型的窗口 -func TestWindowTypes(t *testing.T) { - tests := []struct { - name string - windowType string - windowParams map[string]interface{} - expectError bool - }{ - { - name: "Tumbling Window", - windowType: "tumbling", - windowParams: map[string]interface{}{ - "size": "5s", - }, - expectError: false, - }, - { - name: "Sliding Window", - windowType: "sliding", - windowParams: map[string]interface{}{ - "size": "10s", - "slide": "5s", - }, - expectError: false, - }, - { - name: "Session Window", - windowType: "session", - windowParams: map[string]interface{}{ - "timeout": "30s", - }, - expectError: false, - }, - { - name: "Invalid Window Type", - windowType: "invalid_window_type", - windowParams: map[string]interface{}{"size": "5s"}, - expectError: true, - }, - { - name: "Missing Size Parameter", - windowType: "tumbling", - windowParams: map[string]interface{}{}, - expectError: true, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - config := types.Config{ - NeedWindow: true, - WindowConfig: types.WindowConfig{ - Type: tt.windowType, - Params: tt.windowParams, - }, - SelectFields: map[string]aggregator.AggregateType{ - "value": aggregator.Count, - }, - PerformanceConfig: types.DefaultPerformanceConfig(), - } - - stream, err := NewStream(config) - if tt.expectError { - assert.Error(t, err) - assert.Nil(t, stream) - } else { - assert.NoError(t, err) - assert.NotNil(t, stream) - if stream != nil { - defer stream.Stop() - assert.NotNil(t, stream.Window) - } - } - }) - } -} - -// TestAggregationTypes 测试不同的聚合类型 -func TestAggregationTypes(t *testing.T) { - tests := []struct { - name string - aggType aggregator.AggregateType - testData []float64 - expected float64 - }{ - {"Sum", aggregator.Sum, []float64{1, 2, 3, 4, 5}, 15.0}, - {"Avg", aggregator.Avg, []float64{2, 4, 6, 8}, 5.0}, - {"Min", aggregator.Min, []float64{5, 2, 8, 1, 9}, 1.0}, - {"Max", aggregator.Max, []float64{5, 2, 8, 1, 9}, 9.0}, - {"Count", aggregator.Count, []float64{1, 2, 3}, 3.0}, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - config := types.Config{ - WindowConfig: types.WindowConfig{ - Type: "tumbling", - Params: map[string]interface{}{"size": 500 * time.Millisecond}, - }, - GroupFields: []string{"group"}, - SelectFields: map[string]aggregator.AggregateType{ - "value": tt.aggType, - }, - NeedWindow: true, - } - - stream, err := NewStream(config) - require.NoError(t, err) - defer stream.Stop() - - resultChan := make(chan interface{}, 1) - stream.AddSink(func(result interface{}) { - select { - case resultChan <- result: - default: - } - }) - - stream.Start() - - // 发送测试数据 - for _, value := range tt.testData { - data := map[string]interface{}{ - "group": "test", - "value": value, - } - stream.Emit(data) - } - - // 等待窗口关闭 - time.Sleep(700 * time.Millisecond) - - select { - case result := <-resultChan: - resultSlice := result.([]map[string]interface{}) - require.Len(t, resultSlice, 1) - actual := resultSlice[0]["value"].(float64) - assert.InEpsilon(t, tt.expected, actual, 0.0001) - case <-time.After(3 * time.Second): - t.Fatal("Timeout waiting for aggregation result") - } - }) - } -} \ No newline at end of file +package stream + +import ( + "fmt" + "testing" + "time" + + "github.com/rulego/streamsql/aggregator" + "github.com/rulego/streamsql/types" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// TestWindowSlotAggregation 测试窗口时间槽聚合 +func TestWindowSlotAggregation(t *testing.T) { + config := types.Config{ + WindowConfig: types.WindowConfig{ + Type: "sliding", + Params: map[string]interface{}{"size": 2 * time.Second, "slide": 1 * time.Second}, + TsProp: "ts", + }, + GroupFields: []string{"device"}, + SelectFields: map[string]aggregator.AggregateType{ + "temperature": aggregator.Max, + "humidity": aggregator.Min, + "start": aggregator.WindowStart, + "end": aggregator.WindowEnd, + }, + NeedWindow: true, + } + + strm, err := NewStream(config) + require.NoError(t, err) + defer strm.Stop() + + strm.Start() + + // 使用固定时间戳的测试数据 + baseTime := time.Date(2025, 4, 7, 16, 46, 0, 0, time.UTC) + testData := []map[string]interface{}{ + map[string]interface{}{"device": "aa", "temperature": 25.0, "humidity": 60, "ts": baseTime}, + map[string]interface{}{"device": "aa", "temperature": 30.0, "humidity": 55, "ts": baseTime.Add(1 * time.Second)}, + map[string]interface{}{"device": "bb", "temperature": 22.0, "humidity": 70, "ts": baseTime}, + } + + for _, data := range testData { + strm.Emit(data) + } + + // 捕获结果 + resultChan := make(chan interface{}) + strm.AddSink(func(result []map[string]interface{}) { + resultChan <- result + }) + + // 等待窗口触发 + time.Sleep(3 * time.Second) + + select { + case actual := <-resultChan: + expected := []map[string]interface{}{ + { + "device": "aa", + "temperature": 30.0, + "humidity": 55.0, + "start": baseTime.UnixNano(), + "end": baseTime.Add(2 * time.Second).UnixNano(), + }, + { + "device": "bb", + "temperature": 22.0, + "humidity": 70.0, + "start": baseTime.UnixNano(), + "end": baseTime.Add(2 * time.Second).UnixNano(), + }, + } + + assert.IsType(t, []map[string]interface{}{}, actual) + resultSlice := actual.([]map[string]interface{}) + assert.Len(t, resultSlice, 2) + + for _, expectedResult := range expected { + found := false + for _, resultMap := range resultSlice { + if resultMap["device"] == expectedResult["device"] { + assert.InEpsilon(t, expectedResult["temperature"].(float64), resultMap["temperature"].(float64), 0.0001) + assert.InEpsilon(t, expectedResult["humidity"].(float64), resultMap["humidity"].(float64), 0.0001) + assert.Equal(t, expectedResult["start"].(int64), resultMap["start"].(int64)) + assert.Equal(t, expectedResult["end"].(int64), resultMap["end"].(int64)) + found = true + break + } + } + assert.True(t, found, fmt.Sprintf("Expected result for device %v not found", expectedResult["device"])) + } + case <-time.After(10 * time.Second): + t.Fatal("Timeout waiting for results") + } +} + +// TestWindowTypes 测试不同类型的窗口 +func TestWindowTypes(t *testing.T) { + tests := []struct { + name string + windowType string + windowParams map[string]interface{} + expectError bool + }{ + { + name: "Tumbling Window", + windowType: "tumbling", + windowParams: map[string]interface{}{ + "size": "5s", + }, + expectError: false, + }, + { + name: "Sliding Window", + windowType: "sliding", + windowParams: map[string]interface{}{ + "size": "10s", + "slide": "5s", + }, + expectError: false, + }, + { + name: "Session Window", + windowType: "session", + windowParams: map[string]interface{}{ + "timeout": "30s", + }, + expectError: false, + }, + { + name: "Invalid Window Type", + windowType: "invalid_window_type", + windowParams: map[string]interface{}{"size": "5s"}, + expectError: true, + }, + { + name: "Missing Size Parameter", + windowType: "tumbling", + windowParams: map[string]interface{}{}, + expectError: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + config := types.Config{ + NeedWindow: true, + WindowConfig: types.WindowConfig{ + Type: tt.windowType, + Params: tt.windowParams, + }, + SelectFields: map[string]aggregator.AggregateType{ + "value": aggregator.Count, + }, + PerformanceConfig: types.DefaultPerformanceConfig(), + } + + stream, err := NewStream(config) + if tt.expectError { + assert.Error(t, err) + assert.Nil(t, stream) + } else { + assert.NoError(t, err) + assert.NotNil(t, stream) + if stream != nil { + defer stream.Stop() + assert.NotNil(t, stream.Window) + } + } + }) + } +} + +// TestAggregationTypes 测试不同的聚合类型 +func TestAggregationTypes(t *testing.T) { + tests := []struct { + name string + aggType aggregator.AggregateType + testData []float64 + expected float64 + }{ + {"Sum", aggregator.Sum, []float64{1, 2, 3, 4, 5}, 15.0}, + {"Avg", aggregator.Avg, []float64{2, 4, 6, 8}, 5.0}, + {"Min", aggregator.Min, []float64{5, 2, 8, 1, 9}, 1.0}, + {"Max", aggregator.Max, []float64{5, 2, 8, 1, 9}, 9.0}, + {"Count", aggregator.Count, []float64{1, 2, 3}, 3.0}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + config := types.Config{ + WindowConfig: types.WindowConfig{ + Type: "tumbling", + Params: map[string]interface{}{"size": 500 * time.Millisecond}, + }, + GroupFields: []string{"group"}, + SelectFields: map[string]aggregator.AggregateType{ + "value": tt.aggType, + }, + NeedWindow: true, + } + + stream, err := NewStream(config) + require.NoError(t, err) + defer stream.Stop() + + resultChan := make(chan interface{}, 1) + stream.AddSink(func(result []map[string]interface{}) { + select { + case resultChan <- result: + default: + } + }) + + stream.Start() + + // 发送测试数据 + for _, value := range tt.testData { + data := map[string]interface{}{ + "group": "test", + "value": value, + } + stream.Emit(data) + } + + // 等待窗口关闭 + time.Sleep(700 * time.Millisecond) + + select { + case result := <-resultChan: + resultSlice := result.([]map[string]interface{}) + require.Len(t, resultSlice, 1) + actual := resultSlice[0]["value"].(float64) + assert.InEpsilon(t, tt.expected, actual, 0.0001) + case <-time.After(3 * time.Second): + t.Fatal("Timeout waiting for aggregation result") + } + }) + } +} diff --git a/streamsql.go b/streamsql.go index ee9a378..7c46814 100644 --- a/streamsql.go +++ b/streamsql.go @@ -168,15 +168,11 @@ func (s *Streamsql) Execute(sql string) error { return nil } -// Emit 向流中添加一条数据记录。 -// 数据会根据已配置的SQL查询进行处理和聚合。 -// -// 支持的数据格式: -// - map[string]interface{}: 最常用的键值对格式 -// - 结构体: 会自动转换为map格式处理 +// Emit 添加数据到流处理管道。 +// 接受类型安全的map[string]interface{}格式数据。 // // 参数: -// - data: 要添加的数据,通常是map[string]interface{}或结构体 +// - data: 要添加的数据,必须是map[string]interface{}类型 // // 示例: // @@ -194,50 +190,36 @@ func (s *Streamsql) Execute(sql string) error { // "action": "click", // "page": "/home", // }) -func (s *Streamsql) Emit(data interface{}) { +func (s *Streamsql) Emit(data map[string]interface{}) { if s.stream != nil { s.stream.Emit(data) } } // EmitSync 同步处理数据,立即返回处理结果。 -// 仅适用于非聚合查询(如过滤、转换等),聚合查询会返回错误。 -// -// 对于非聚合查询,此方法提供同步的数据处理能力,同时: -// 1. 立即返回处理结果(同步) -// 2. 触发已注册的AddSink回调(异步) -// -// 这确保了同步和异步模式的一致性,用户可以同时获得: -// - 立即可用的处理结果 -// - 异步回调处理(用于日志、监控、持久化等) +// 仅适用于非聚合查询,聚合查询会返回错误。 +// 接受类型安全的map[string]interface{}格式数据。 // // 参数: -// - data: 要处理的数据 +// - data: 要处理的数据,必须是map[string]interface{}类型 // // 返回值: -// - interface{}: 处理后的结果,如果不匹配过滤条件返回nil -// - error: 处理错误,如果是聚合查询会返回错误 +// - map[string]interface{}: 处理后的结果数据,如果不匹配过滤条件返回nil +// - error: 处理错误 // // 示例: // -// // 添加日志回调 -// ssql.AddSink(func(result interface{}) { -// fmt.Printf("异步日志: %v\n", result) -// }) -// -// // 同步处理并立即获取结果 // result, err := ssql.EmitSync(map[string]interface{}{ +// "deviceId": "sensor001", // "temperature": 25.5, -// "humidity": 60.0, // }) // if err != nil { -// // 处理错误 +// log.Printf("处理错误: %v", err) // } else if result != nil { -// // 立即使用处理结果 -// fmt.Printf("同步结果: %v\n", result) -// // 同时异步回调也会被触发 +// // 立即使用处理结果(result是map[string]interface{}类型) +// fmt.Printf("处理结果: %v\n", result) // } -func (s *Streamsql) EmitSync(data interface{}) (interface{}, error) { +func (s *Streamsql) EmitSync(data map[string]interface{}) (map[string]interface{}, error) { if s.stream == nil { return nil, fmt.Errorf("stream未初始化") } @@ -272,8 +254,8 @@ func (s *Streamsql) IsAggregationQuery() bool { // 示例: // // // 添加结果处理回调 -// ssql.Stream().AddSink(func(result interface{}) { -// fmt.Printf("处理结果: %v\n", result) +// ssql.Stream().AddSink(func(results []map[string]interface{}) { +// fmt.Printf("处理结果: %v\n", results) // }) // // // 获取结果通道 @@ -321,25 +303,25 @@ func (s *Streamsql) Stop() { // 这是对 Stream().AddSink() 的便捷封装,使API调用更简洁。 // // 参数: -// - sink: 结果处理函数,接收处理结果作为参数 +// - sink: 结果处理函数,接收[]map[string]interface{}类型的结果数据 // // 示例: // // // 直接添加结果处理 -// ssql.AddSink(func(result interface{}) { -// fmt.Printf("处理结果: %v\n", result) +// ssql.AddSink(func(results []map[string]interface{}) { +// fmt.Printf("处理结果: %v\n", results) // }) // // // 添加多个处理器 -// ssql.AddSink(func(result interface{}) { +// ssql.AddSink(func(results []map[string]interface{}) { // // 保存到数据库 -// saveToDatabase(result) +// saveToDatabase(results) // }) -// ssql.AddSink(func(result interface{}) { +// ssql.AddSink(func(results []map[string]interface{}) { // // 发送到消息队列 -// sendToQueue(result) +// sendToQueue(results) // }) -func (s *Streamsql) AddSink(sink func(interface{})) { +func (s *Streamsql) AddSink(sink func([]map[string]interface{})) { if s.stream != nil { s.stream.AddSink(sink) } @@ -366,14 +348,16 @@ func (s *Streamsql) AddSink(sink func(interface{})) { // // | bb | 22.0 | // // +--------+----------+ func (s *Streamsql) PrintTable() { - s.AddSink(func(result interface{}) { - s.printTableFormat(result) + s.AddSink(func(results []map[string]interface{}) { + s.printTableFormat(results) }) } // printTableFormat 格式化打印表格数据 -func (s *Streamsql) printTableFormat(result interface{}) { - table.FormatTableData(result, s.fieldOrder) +// 参数: +// - results: []map[string]interface{}类型的结果数据 +func (s *Streamsql) printTableFormat(results []map[string]interface{}) { + table.FormatTableData(results, s.fieldOrder) } // ToChannel 返回结果通道,用于异步获取处理结果。 @@ -393,10 +377,14 @@ func (s *Streamsql) printTableFormat(result interface{}) { // } // }() // } + +// ToChannel 将查询结果转换为通道输出 +// 返回一个只读通道,用于接收查询结果 // // 注意: // - 必须有消费者持续从通道读取数据,否则可能导致流处理阻塞 -func (s *Streamsql) ToChannel() <-chan interface{} { +// - 返回的通道传输批量结果数据 +func (s *Streamsql) ToChannel() <-chan []map[string]interface{} { if s.stream != nil { return s.stream.GetResultsChan() } diff --git a/streamsql_benchmark_test.go b/streamsql_benchmark_test.go index 328ebcc..2db58fa 100644 --- a/streamsql_benchmark_test.go +++ b/streamsql_benchmark_test.go @@ -50,7 +50,7 @@ func BenchmarkStreamSQLCore(b *testing.B) { var resultReceived int64 // 添加结果处理器 - ssql.AddSink(func(result interface{}) { + ssql.AddSink(func(result []map[string]interface{}) { atomic.AddInt64(&resultReceived, 1) }) @@ -150,7 +150,7 @@ func BenchmarkConfigComparison(b *testing.B) { } var resultCount int64 - ssql.AddSink(func(result interface{}) { + ssql.AddSink(func(result []map[string]interface{}) { atomic.AddInt64(&resultCount, 1) }) @@ -322,7 +322,7 @@ func BenchmarkConfigurationComparison(b *testing.B) { var resultCount int64 // 添加轻量级sink - ssql.AddSink(func(result interface{}) { + ssql.AddSink(func(result []map[string]interface{}) { atomic.AddInt64(&resultCount, 1) }) @@ -547,7 +547,7 @@ func BenchmarkLightweightVsDefaultComparison(b *testing.B) { } var resultCount int64 - ssql.AddSink(func(result interface{}) { + ssql.AddSink(func(result []map[string]interface{}) { atomic.AddInt64(&resultCount, 1) }) @@ -641,7 +641,7 @@ func BenchmarkStreamSQLRealistic(b *testing.B) { var actualResultCount int64 // 测量实际的处理完成 - ssql.AddSink(func(result interface{}) { + ssql.AddSink(func(result []map[string]interface{}) { atomic.AddInt64(&actualResultCount, 1) }) @@ -805,7 +805,7 @@ func BenchmarkEndToEndProcessing(b *testing.B) { resultChan := make(chan bool, currentBatchSize) // 设置sink来捕获结果 - ssql.AddSink(func(result interface{}) { + ssql.AddSink(func(result []map[string]interface{}) { count := atomic.AddInt64(&resultsReceived, 1) if count <= int64(currentBatchSize) { resultChan <- true @@ -881,7 +881,7 @@ func BenchmarkSustainedProcessing(b *testing.B) { var lastResultTime time.Time // 设置结果处理器 - ssql.AddSink(func(result interface{}) { + ssql.AddSink(func(result []map[string]interface{}) { atomic.AddInt64(&processedResults, 1) lastResultTime = time.Now() }) diff --git a/streamsql_case_test.go b/streamsql_case_test.go index 66c5978..23404fa 100644 --- a/streamsql_case_test.go +++ b/streamsql_case_test.go @@ -39,14 +39,10 @@ func TestCaseExpressionInSQL(t *testing.T) { // 添加数据并获取结果 var results []map[string]interface{} var resultsMutex sync.Mutex - streamSQL.stream.AddSink(func(result interface{}) { + streamSQL.stream.AddSink(func(result []map[string]interface{}) { resultsMutex.Lock() defer resultsMutex.Unlock() - if resultSlice, ok := result.([]map[string]interface{}); ok { - results = append(results, resultSlice...) - } else if resultMap, ok := result.(map[string]interface{}); ok { - results = append(results, resultMap) - } + results = append(results, result...) }) for _, data := range testData { @@ -93,12 +89,10 @@ func TestCaseExpressionInAggregation(t *testing.T) { // 添加数据并获取结果 var results []map[string]interface{} var resultsMutex sync.Mutex - streamSQL.stream.AddSink(func(result interface{}) { + streamSQL.stream.AddSink(func(result []map[string]interface{}) { resultsMutex.Lock() defer resultsMutex.Unlock() - if resultSlice, ok := result.([]map[string]interface{}); ok { - results = append(results, resultSlice...) - } + results = append(results, result...) }) for _, data := range testData { @@ -265,12 +259,10 @@ func TestComplexCaseExpressionsInAggregation(t *testing.T) { // 添加数据并获取结果 var results []map[string]interface{} var resultsMutex sync.Mutex - streamSQL.stream.AddSink(func(result interface{}) { - if resultSlice, ok := result.([]map[string]interface{}); ok { - resultsMutex.Lock() - results = append(results, resultSlice...) - resultsMutex.Unlock() - } + streamSQL.stream.AddSink(func(result []map[string]interface{}) { + resultsMutex.Lock() + defer resultsMutex.Unlock() + results = append(results, result...) }) for _, data := range tc.data { @@ -382,7 +374,7 @@ func TestCaseExpressionNonAggregated(t *testing.T) { // 捕获结果 resultChan := make(chan interface{}, 10) - strm.AddSink(func(result interface{}) { + strm.AddSink(func(result []map[string]interface{}) { select { case resultChan <- result: default: @@ -474,7 +466,7 @@ func TestCaseExpressionAggregated(t *testing.T) { // 使用通道等待结果,避免固定等待时间 resultChan := make(chan interface{}, 5) - strm.AddSink(func(result interface{}) { + strm.AddSink(func(result []map[string]interface{}) { select { case resultChan <- result: default: @@ -606,7 +598,7 @@ func TestCaseExpressionNullHandlingInAggregation(t *testing.T) { var results []map[string]interface{} resultChan := make(chan interface{}, 10) - ssql.AddSink(func(result interface{}) { + ssql.AddSink(func(result []map[string]interface{}) { resultChan <- result }) @@ -768,12 +760,10 @@ func TestHavingWithCaseExpressionFunctional(t *testing.T) { // 添加数据并获取结果 var results []map[string]interface{} var resultsMutex sync.Mutex - streamSQL.stream.AddSink(func(result interface{}) { + streamSQL.stream.AddSink(func(result []map[string]interface{}) { resultsMutex.Lock() defer resultsMutex.Unlock() - if resultSlice, ok := result.([]map[string]interface{}); ok { - results = append(results, resultSlice...) - } + results = append(results, result...) }) for _, data := range testData { @@ -854,14 +844,10 @@ func TestNegativeNumberInSQL(t *testing.T) { var results []map[string]interface{} var resultsMutex sync.Mutex - streamSQL.stream.AddSink(func(result interface{}) { + streamSQL.stream.AddSink(func(result []map[string]interface{}) { resultsMutex.Lock() defer resultsMutex.Unlock() - if resultSlice, ok := result.([]map[string]interface{}); ok { - results = append(results, resultSlice...) - } else if resultMap, ok := result.(map[string]interface{}); ok { - results = append(results, resultMap) - } + results = append(results, result...) }) // 添加测试数据 diff --git a/streamsql_custom_functions_test.go b/streamsql_custom_functions_test.go index 4f0f0d3..a142193 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.AddSink(func(result interface{}) { + streamsql.AddSink(func(result []map[string]interface{}) { resultChan <- result }) @@ -179,7 +179,7 @@ func TestCustomStringFunctions(t *testing.T) { // 创建结果接收通道 resultChan := make(chan interface{}, 10) - streamsql.AddSink(func(result interface{}) { + streamsql.AddSink(func(result []map[string]interface{}) { resultChan <- result }) @@ -318,16 +318,16 @@ func TestCustomAggregateFunctions(t *testing.T) { // 创建结果接收通道 resultChan := make(chan interface{}, 10) - streamsql.AddSink(func(result interface{}) { + streamsql.AddSink(func(result []map[string]interface{}) { resultChan <- result }) // 添加测试数据 - testData := []interface{}{ - map[string]interface{}{"device": "sensor1", "value": 2.0, "category": "A"}, - map[string]interface{}{"device": "sensor1", "value": 8.0, "category": "A"}, - map[string]interface{}{"device": "sensor1", "value": 32.0, "category": "B"}, - map[string]interface{}{"device": "sensor1", "value": 128.0, "category": "A"}, + testData := []map[string]interface{}{ + {"device": "sensor1", "value": 2.0, "category": "A"}, + {"device": "sensor1", "value": 8.0, "category": "A"}, + {"device": "sensor1", "value": 32.0, "category": "B"}, + {"device": "sensor1", "value": 128.0, "category": "A"}, } for _, data := range testData { @@ -557,14 +557,14 @@ func TestCustomFunctionWithAggregation(t *testing.T) { // 创建结果接收通道 resultChan := make(chan interface{}, 10) - streamsql.AddSink(func(result interface{}) { + streamsql.AddSink(func(result []map[string]interface{}) { resultChan <- result }) // 添加测试数据(摄氏度) - testData := []interface{}{ - map[string]interface{}{"device": "thermometer", "temperature": 0.0}, // 32°F - map[string]interface{}{"device": "thermometer", "temperature": 100.0}, // 212°F + testData := []map[string]interface{}{ + {"device": "thermometer", "temperature": 0.0}, // 32°F + {"device": "thermometer", "temperature": 100.0}, // 212°F } for _, data := range testData { diff --git a/streamsql_function_integration_test.go b/streamsql_function_integration_test.go index 9c45246..7b41f46 100644 --- a/streamsql_function_integration_test.go +++ b/streamsql_function_integration_test.go @@ -22,7 +22,7 @@ func TestFunctionIntegrationNonAggregation(t *testing.T) { strm := streamsql.stream resultChan := make(chan interface{}, 10) - strm.AddSink(func(result interface{}) { + strm.AddSink(func(result []map[string]interface{}) { resultChan <- result }) @@ -68,7 +68,7 @@ func TestFunctionIntegrationNonAggregation(t *testing.T) { strm := streamsql.stream resultChan := make(chan interface{}, 10) - strm.AddSink(func(result interface{}) { + strm.AddSink(func(result []map[string]interface{}) { resultChan <- result }) @@ -110,7 +110,7 @@ func TestFunctionIntegrationNonAggregation(t *testing.T) { strm := streamsql.stream resultChan := make(chan interface{}, 10) - strm.AddSink(func(result interface{}) { + strm.AddSink(func(result []map[string]interface{}) { resultChan <- result }) @@ -152,7 +152,7 @@ func TestFunctionIntegrationNonAggregation(t *testing.T) { strm := streamsql.stream resultChan := make(chan interface{}, 10) - strm.AddSink(func(result interface{}) { + strm.AddSink(func(result []map[string]interface{}) { resultChan <- result }) @@ -195,7 +195,7 @@ func TestFunctionIntegrationNonAggregation(t *testing.T) { strm := streamsql.stream resultChan := make(chan interface{}, 10) - strm.AddSink(func(result interface{}) { + strm.AddSink(func(result []map[string]interface{}) { resultChan <- result }) @@ -239,17 +239,17 @@ func TestFunctionIntegrationAggregation(t *testing.T) { strm := streamsql.stream resultChan := make(chan interface{}, 10) - strm.AddSink(func(result interface{}) { + strm.AddSink(func(result []map[string]interface{}) { resultChan <- result }) // 添加测试数据 - testData := []interface{}{ - map[string]interface{}{"device": "sensor1", "temperature": 20.0}, - map[string]interface{}{"device": "sensor1", "temperature": 25.0}, - map[string]interface{}{"device": "sensor1", "temperature": 30.0}, - map[string]interface{}{"device": "sensor2", "temperature": 15.0}, - map[string]interface{}{"device": "sensor2", "temperature": 18.0}, + testData := []map[string]interface{}{ + {"device": "sensor1", "temperature": 20.0}, + {"device": "sensor1", "temperature": 25.0}, + {"device": "sensor1", "temperature": 30.0}, + {"device": "sensor2", "temperature": 15.0}, + {"device": "sensor2", "temperature": 18.0}, } for _, data := range testData { @@ -303,17 +303,17 @@ func TestFunctionIntegrationAggregation(t *testing.T) { strm := streamsql.stream resultChan := make(chan interface{}, 10) - strm.AddSink(func(result interface{}) { + strm.AddSink(func(result []map[string]interface{}) { resultChan <- result }) // 添加测试数据 - testData := []interface{}{ - map[string]interface{}{"device": "sensor1", "temperature": 10.0}, - map[string]interface{}{"device": "sensor1", "temperature": 20.0}, - map[string]interface{}{"device": "sensor1", "temperature": 30.0}, - map[string]interface{}{"device": "sensor1", "temperature": 40.0}, - map[string]interface{}{"device": "sensor1", "temperature": 50.0}, + testData := []map[string]interface{}{ + {"device": "sensor1", "temperature": 10.0}, + {"device": "sensor1", "temperature": 20.0}, + {"device": "sensor1", "temperature": 30.0}, + {"device": "sensor1", "temperature": 40.0}, + {"device": "sensor1", "temperature": 50.0}, } for _, data := range testData { @@ -356,15 +356,15 @@ func TestFunctionIntegrationAggregation(t *testing.T) { strm := streamsql.stream resultChan := make(chan interface{}, 10) - strm.AddSink(func(result interface{}) { + strm.AddSink(func(result []map[string]interface{}) { resultChan <- result }) // 添加测试数据 - testData := []interface{}{ - map[string]interface{}{"device": "sensor1", "temperature": 20.0}, - map[string]interface{}{"device": "sensor1", "temperature": 25.0}, - map[string]interface{}{"device": "sensor1", "temperature": 30.0}, + testData := []map[string]interface{}{ + {"device": "sensor1", "temperature": 20.0}, + {"device": "sensor1", "temperature": 25.0}, + {"device": "sensor1", "temperature": 30.0}, } for _, data := range testData { @@ -418,15 +418,15 @@ func TestFunctionIntegrationMixed(t *testing.T) { strm := streamsql.stream resultChan := make(chan interface{}, 10) - strm.AddSink(func(result interface{}) { + strm.AddSink(func(result []map[string]interface{}) { resultChan <- result }) // 添加测试数据 - testData := []interface{}{ - map[string]interface{}{"device": "sensor1", "temperature": 20.567}, - map[string]interface{}{"device": "sensor1", "temperature": 25.234}, - map[string]interface{}{"device": "sensor1", "temperature": 30.123}, + testData := []map[string]interface{}{ + {"device": "sensor1", "temperature": 20.567}, + {"device": "sensor1", "temperature": 25.234}, + {"device": "sensor1", "temperature": 30.123}, } for _, data := range testData { @@ -488,7 +488,7 @@ func TestFunctionIntegrationMixed(t *testing.T) { strm := streamsql.stream resultChan := make(chan interface{}, 10) - strm.AddSink(func(result interface{}) { + strm.AddSink(func(result []map[string]interface{}) { resultChan <- result }) @@ -529,14 +529,14 @@ func TestFunctionIntegrationMixed(t *testing.T) { strm := streamsql.stream resultChan := make(chan interface{}, 10) - strm.AddSink(func(result interface{}) { + strm.AddSink(func(result []map[string]interface{}) { resultChan <- result }) // 添加测试数据 - testData := []interface{}{ - map[string]interface{}{"device": "sensor1", "temperature": 20.0}, - map[string]interface{}{"device": "sensor1", "temperature": 30.0}, + testData := []map[string]interface{}{ + {"device": "sensor1", "temperature": 20.0}, + {"device": "sensor1", "temperature": 30.0}, } for _, data := range testData { @@ -582,15 +582,15 @@ func TestNestedFunctionSupport(t *testing.T) { strm := streamsql.stream resultChan := make(chan interface{}, 10) - strm.AddSink(func(result interface{}) { + strm.AddSink(func(result []map[string]interface{}) { resultChan <- result }) // 添加测试数据 - testData := []interface{}{ - map[string]interface{}{"device": "sensor1", "temperature": 20.567}, - map[string]interface{}{"device": "sensor1", "temperature": 25.234}, - map[string]interface{}{"device": "sensor1", "temperature": 30.123}, + testData := []map[string]interface{}{ + {"device": "sensor1", "temperature": 20.567}, + {"device": "sensor1", "temperature": 25.234}, + {"device": "sensor1", "temperature": 30.123}, } for _, data := range testData { @@ -647,15 +647,15 @@ func TestNestedFunctionSupport(t *testing.T) { strm := streamsql.stream resultChan := make(chan interface{}, 10) - strm.AddSink(func(result interface{}) { + strm.AddSink(func(result []map[string]interface{}) { resultChan <- result }) // 添加测试数据 - testData := []interface{}{ - map[string]interface{}{"device": "sensor1", "temperature": 20.567}, // round(20.567, 2) = 20.57 - map[string]interface{}{"device": "sensor1", "temperature": 25.234}, // round(25.234, 2) = 25.23 - map[string]interface{}{"device": "sensor1", "temperature": 30.123}, // round(30.123, 2) = 30.12 + testData := []map[string]interface{}{ + {"device": "sensor1", "temperature": 20.567}, // round(20.567, 2) = 20.57 + {"device": "sensor1", "temperature": 25.234}, // round(25.234, 2) = 25.23 + {"device": "sensor1", "temperature": 30.123}, // round(30.123, 2) = 30.12 } for _, data := range testData { @@ -716,15 +716,15 @@ func TestNestedFunctionSupport(t *testing.T) { strm := streamsql.stream resultChan := make(chan interface{}, 10) - strm.AddSink(func(result interface{}) { + strm.AddSink(func(result []map[string]interface{}) { resultChan <- result }) // 添加测试数据(包含负数) - testData := []interface{}{ - map[string]interface{}{"device": "sensor1", "temperature": -20.567}, // abs(-20.567) = 20.567 - map[string]interface{}{"device": "sensor1", "temperature": 25.234}, // abs(25.234) = 25.234 - map[string]interface{}{"device": "sensor1", "temperature": -30.123}, // abs(-30.123) = 30.123 + testData := []map[string]interface{}{ + {"device": "sensor1", "temperature": -20.567}, // abs(-20.567) = 20.567 + {"device": "sensor1", "temperature": 25.234}, // abs(25.234) = 25.234 + {"device": "sensor1", "temperature": -30.123}, // abs(-30.123) = 30.123 } for _, data := range testData { @@ -789,7 +789,7 @@ func TestNestedFunctionExecutionOrder(t *testing.T) { strm := streamsql.stream resultChan := make(chan interface{}, 10) - strm.AddSink(func(result interface{}) { + strm.AddSink(func(result []map[string]interface{}) { resultChan <- result }) @@ -829,7 +829,7 @@ func TestNestedFunctionExecutionOrder(t *testing.T) { strm := streamsql.stream resultChan := make(chan interface{}, 10) - strm.AddSink(func(result interface{}) { + strm.AddSink(func(result []map[string]interface{}) { resultChan <- result }) @@ -869,7 +869,7 @@ func TestNestedFunctionExecutionOrder(t *testing.T) { strm := streamsql.stream resultChan := make(chan interface{}, 10) - strm.AddSink(func(result interface{}) { + strm.AddSink(func(result []map[string]interface{}) { resultChan <- result }) @@ -909,15 +909,15 @@ func TestNestedFunctionExecutionOrder(t *testing.T) { strm := streamsql.stream resultChan := make(chan interface{}, 10) - strm.AddSink(func(result interface{}) { + strm.AddSink(func(result []map[string]interface{}) { resultChan <- result }) // 添加测试数据 - testData := []interface{}{ - map[string]interface{}{"device": "sensor1", "temperature": 20.567}, - map[string]interface{}{"device": "sensor1", "temperature": 25.234}, - map[string]interface{}{"device": "sensor1", "temperature": 30.123}, + testData := []map[string]interface{}{ + {"device": "sensor1", "temperature": 20.567}, + {"device": "sensor1", "temperature": 25.234}, + {"device": "sensor1", "temperature": 30.123}, } for _, data := range testData { @@ -961,7 +961,7 @@ func TestNestedFunctionExecutionOrder(t *testing.T) { strm := streamsql.stream resultChan := make(chan interface{}, 10) - strm.AddSink(func(result interface{}) { + strm.AddSink(func(result []map[string]interface{}) { resultChan <- result }) @@ -1001,7 +1001,7 @@ func TestNestedFunctionExecutionOrder(t *testing.T) { strm := streamsql.stream resultChan := make(chan interface{}, 10) - strm.AddSink(func(result interface{}) { + strm.AddSink(func(result []map[string]interface{}) { resultChan <- result }) diff --git a/streamsql_is_null_test.go b/streamsql_is_null_test.go index 9334ca7..bdc7332 100644 --- a/streamsql_is_null_test.go +++ b/streamsql_is_null_test.go @@ -117,7 +117,7 @@ func TestIsNullOperatorInSQL(t *testing.T) { resultChan := make(chan interface{}, 10) resultsMutex := sync.Mutex{} - ssql.Stream().AddSink(func(result interface{}) { + ssql.Stream().AddSink(func(result []map[string]interface{}) { resultChan <- result }) @@ -208,7 +208,7 @@ func TestIsNullInAggregation(t *testing.T) { // 收集结果 resultChan := make(chan interface{}, 10) - ssql.Stream().AddSink(func(result interface{}) { + ssql.Stream().AddSink(func(result []map[string]interface{}) { resultChan <- result }) @@ -274,7 +274,7 @@ func TestIsNullInHaving(t *testing.T) { require.NoError(t, err) resultChan := make(chan interface{}, 10) - ssql.Stream().AddSink(func(result interface{}) { + ssql.Stream().AddSink(func(result []map[string]interface{}) { resultChan <- result }) @@ -339,7 +339,7 @@ func TestIsNullInHavingWithIsNotNull(t *testing.T) { require.NoError(t, err) resultChan := make(chan interface{}, 10) - ssql.Stream().AddSink(func(result interface{}) { + ssql.Stream().AddSink(func(result []map[string]interface{}) { resultChan <- result }) @@ -405,7 +405,7 @@ func TestIsNullWithOtherOperators(t *testing.T) { require.NoError(t, err) resultChan := make(chan interface{}, 10) - ssql.Stream().AddSink(func(result interface{}) { + ssql.Stream().AddSink(func(result []map[string]interface{}) { resultChan <- result }) @@ -572,7 +572,7 @@ func TestCaseWhenWithIsNull(t *testing.T) { var results []map[string]interface{} resultChan := make(chan interface{}, 10) - ssql.Stream().AddSink(func(result interface{}) { + ssql.Stream().AddSink(func(result []map[string]interface{}) { resultChan <- result }) @@ -835,7 +835,7 @@ func TestNullComparisons(t *testing.T) { var results []map[string]interface{} resultChan := make(chan interface{}, 10) - ssql.Stream().AddSink(func(result interface{}) { + ssql.Stream().AddSink(func(result []map[string]interface{}) { resultChan <- result }) @@ -920,7 +920,7 @@ func TestNullComparisonInAggregation(t *testing.T) { // 收集结果 resultChan := make(chan interface{}, 10) - ssql.Stream().AddSink(func(result interface{}) { + ssql.Stream().AddSink(func(result []map[string]interface{}) { resultChan <- result }) @@ -984,7 +984,7 @@ func TestMixedNullComparisons(t *testing.T) { require.NoError(t, err) resultChan := make(chan interface{}, 10) - ssql.Stream().AddSink(func(result interface{}) { + ssql.Stream().AddSink(func(result []map[string]interface{}) { resultChan <- result }) diff --git a/streamsql_like_test.go b/streamsql_like_test.go index a7c2701..21a954f 100644 --- a/streamsql_like_test.go +++ b/streamsql_like_test.go @@ -28,16 +28,16 @@ func TestLikeOperatorInSQL(t *testing.T) { resultChan := make(chan interface{}, 10) // 添加结果回调 - strm.AddSink(func(result interface{}) { + strm.AddSink(func(result []map[string]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"}, + testData := []map[string]interface{}{ + {"deviceId": "sensor001", "deviceType": "temperature"}, + {"deviceId": "device002", "deviceType": "humidity"}, + {"deviceId": "sensor003", "deviceType": "pressure"}, + {"deviceId": "pump004", "deviceType": "actuator"}, } // 添加数据 @@ -86,15 +86,15 @@ func TestLikeOperatorInSQL(t *testing.T) { strm := streamsql.stream resultChan := make(chan interface{}, 10) - strm.AddSink(func(result interface{}) { + strm.AddSink(func(result []map[string]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"}, + testData := []map[string]interface{}{ + {"deviceId": "dev1", "status": "connection_error"}, + {"deviceId": "dev2", "status": "running"}, + {"deviceId": "dev3", "status": "timeout_error"}, + {"deviceId": "dev4", "status": "normal"}, } for _, data := range testData { @@ -141,15 +141,15 @@ func TestLikeOperatorInSQL(t *testing.T) { strm := streamsql.stream resultChan := make(chan interface{}, 10) - strm.AddSink(func(result interface{}) { + strm.AddSink(func(result []map[string]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"}, + testData := []map[string]interface{}{ + {"deviceId": "dev1", "message": "system alert: high temperature"}, + {"deviceId": "dev2", "message": "normal operation"}, + {"deviceId": "dev3", "message": "critical alert detected"}, + {"deviceId": "dev4", "message": "info: device startup"}, } for _, data := range testData { @@ -196,15 +196,15 @@ func TestLikeOperatorInSQL(t *testing.T) { strm := streamsql.stream resultChan := make(chan interface{}, 10) - strm.AddSink(func(result interface{}) { + strm.AddSink(func(result []map[string]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"}, + testData := []map[string]interface{}{ + {"deviceId": "dev1", "code": "E101"}, + {"deviceId": "dev2", "code": "E202"}, + {"deviceId": "dev3", "code": "E305"}, + {"deviceId": "dev4", "code": "F101"}, } for _, data := range testData { @@ -241,15 +241,15 @@ func TestLikeOperatorInSQL(t *testing.T) { strm := streamsql.stream resultChan := make(chan interface{}, 10) - strm.AddSink(func(result interface{}) { + strm.AddSink(func(result []map[string]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"}, + testData := []map[string]interface{}{ + {"deviceId": "dev1", "filename": "system.log"}, + {"deviceId": "dev2", "filename": "config.txt"}, + {"deviceId": "dev3", "filename": "error.log"}, + {"deviceId": "dev4", "filename": "backup.bak"}, } for _, data := range testData { @@ -296,16 +296,16 @@ func TestLikeOperatorInSQL(t *testing.T) { strm := streamsql.stream resultChan := make(chan interface{}, 10) - strm.AddSink(func(result interface{}) { + strm.AddSink(func(result []map[string]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"}, + testData := []map[string]interface{}{ + {"deviceId": "sensor001", "deviceType": "temperature"}, + {"deviceId": "sensor002", "deviceType": "temperature"}, + {"deviceId": "device003", "deviceType": "temperature"}, + {"deviceId": "sensor004", "deviceType": "humidity"}, + {"deviceId": "pump005", "deviceType": "actuator"}, } for _, data := range testData { @@ -358,15 +358,15 @@ func TestLikeOperatorInSQL(t *testing.T) { strm := streamsql.stream resultChan := make(chan interface{}, 10) - strm.AddSink(func(result interface{}) { + strm.AddSink(func(result []map[string]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}, + testData := []map[string]interface{}{ + {"deviceType": "temperature_sensor", "temperature": 25.0}, + {"deviceType": "temperature_sensor", "temperature": 30.0}, + {"deviceType": "humidity_sensor", "temperature": 22.0}, + {"deviceType": "pressure_gauge", "temperature": 20.0}, } for _, data := range testData { @@ -419,15 +419,15 @@ func TestLikeFunctionEquivalence(t *testing.T) { assert.Nil(t, err) resultChan := make(chan interface{}, 10) - streamsql.stream.AddSink(func(result interface{}) { + streamsql.stream.AddSink(func(result []map[string]interface{}) { resultChan <- result }) // 测试数据 - testData := []interface{}{ - map[string]interface{}{"deviceId": "sensor001"}, - map[string]interface{}{"deviceId": "device002"}, - map[string]interface{}{"deviceId": "sensor003"}, + testData := []map[string]interface{}{ + {"deviceId": "sensor001"}, + {"deviceId": "device002"}, + {"deviceId": "sensor003"}, } // 添加数据 @@ -521,7 +521,7 @@ func TestLikePatternMatching(t *testing.T) { assert.Nil(t, err) resultChan := make(chan interface{}, 10) - streamsql.stream.AddSink(func(result interface{}) { + streamsql.stream.AddSink(func(result []map[string]interface{}) { resultChan <- result }) diff --git a/streamsql_nested_field_test.go b/streamsql_nested_field_test.go new file mode 100644 index 0000000..e7ba2c4 --- /dev/null +++ b/streamsql_nested_field_test.go @@ -0,0 +1,314 @@ +package streamsql + +import ( + "context" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// TestComprehensiveNestedFieldAccess 全面测试嵌套字段访问功能 +func TestComprehensiveNestedFieldAccess(t *testing.T) { + t.Run("多层嵌套字段访问", func(t *testing.T) { + streamsql := New() + defer func() { + if streamsql != nil { + streamsql.Stop() + } + }() + + // 测试多层嵌套字段访问 + var rsql = "SELECT device.info.name as device_name, device.location.building as building, sensor.data.temperature as temp FROM stream" + err := streamsql.Execute(rsql) + assert.Nil(t, err, "多层嵌套字段SQL应该能够执行") + + require.NoError(t, err, "多层嵌套字段访问不应该出错") + + strm := streamsql.stream + + // 创建结果接收通道 + resultChan := make(chan interface{}, 10) + + // 添加结果接收器 + strm.AddSink(func(result []map[string]interface{}) { + resultChan <- result + }) + + // 添加带多层嵌套字段的测试数据 + testData := 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", + "room": "301", + }, + }, + "sensor": map[string]interface{}{ + "data": map[string]interface{}{ + "temperature": 28.5, + "humidity": 65.0, + "pressure": 1013.25, + }, + "status": "online", + }, + } + + // 发送数据 + strm.Emit(testData) + + // 等待结果 + ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) + defer cancel() + + select { + case result := <-resultChan: + // 验证结果 + resultSlice, ok := result.([]map[string]interface{}) + require.True(t, ok, "结果应该是[]map[string]interface{}类型") + require.Len(t, resultSlice, 1, "应该只有一条结果") + + item := resultSlice[0] + + // 检查各个嵌套字段的提取情况 + deviceName, deviceNameExists := item["device_name"] + assert.True(t, deviceNameExists, "device.info.name字段应该存在") + assert.Equal(t, "温度传感器001", deviceName, "device.info.name应该被正确提取") + + building, buildingExists := item["building"] + assert.True(t, buildingExists, "device.location.building字段应该存在") + assert.Equal(t, "A栋", building, "device.location.building应该被正确提取") + + temp, tempExists := item["temp"] + assert.True(t, tempExists, "sensor.data.temperature字段应该存在") + assert.Equal(t, 28.5, temp, "sensor.data.temperature应该被正确提取") + + case <-ctx.Done(): + t.Fatal("多层嵌套测试超时") + } + }) + + t.Run("嵌套字段聚合查询", func(t *testing.T) { + streamsql := New() + defer func() { + if streamsql != nil { + streamsql.Stop() + } + }() + + // 测试聚合查询中的嵌套字段 + var rsql = "SELECT device.type, AVG(sensor.temperature) as avg_temp, COUNT(*) as cnt FROM stream GROUP BY device.type, TumblingWindow('1s')" + err := streamsql.Execute(rsql) + assert.Nil(t, err, "嵌套字段聚合SQL应该能够执行") + + require.NoError(t, err, "嵌套字段聚合查询不应该出错") + + strm := streamsql.stream + + // 创建结果接收通道 + resultChan := make(chan interface{}, 10) + + // 添加结果回调 + strm.AddSink(func(result []map[string]interface{}) { + resultChan <- result + }) + + // 添加测试数据 + testData := []map[string]interface{}{ + { + "device": map[string]interface{}{ + "type": "temperature", + "id": "sensor001", + }, + "sensor": map[string]interface{}{ + "temperature": 25.0, + }, + }, + { + "device": map[string]interface{}{ + "type": "temperature", + "id": "sensor002", + }, + "sensor": map[string]interface{}{ + "temperature": 35.0, + }, + }, + { + "device": map[string]interface{}{ + "type": "humidity", + "id": "sensor003", + }, + "sensor": map[string]interface{}{ + "temperature": 20.0, + }, + }, + } + + // 添加数据 + for _, data := range testData { + strm.Emit(data) + } + + // 等待窗口触发 + time.Sleep(1200 * time.Millisecond) + + // 等待结果 + ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) + defer cancel() + + select { + case result := <-resultChan: + // 验证聚合结果 + resultSlice, ok := result.([]map[string]interface{}) + require.True(t, ok, "结果应该是[]map[string]interface{}类型") + + // 聚合查询可能返回空结果,这是正常的 + if len(resultSlice) > 0 { + // 如果有结果,验证结果结构 + for _, item := range resultSlice { + // 检查是否包含任何聚合字段 + hasAnyField := false + if _, exists := item["type"]; exists { + hasAnyField = true + } + if _, exists := item["avg_temp"]; exists { + hasAnyField = true + } + if _, exists := item["cnt"]; exists { + hasAnyField = true + } + assert.True(t, hasAnyField, "聚合结果应该包含至少一个预期字段") + } + } + + case <-ctx.Done(): + t.Fatal("嵌套字段聚合查询测试超时") + } + }) + + t.Run("复杂嵌套字段WHERE条件", func(t *testing.T) { + streamsql := New() + defer func() { + if streamsql != nil { + streamsql.Stop() + } + }() + + // 测试复杂的WHERE条件 + var rsql = "SELECT * FROM stream WHERE device.info.status = 'active' AND sensor.data.temperature > 25 AND device.location.building = 'A栋'" + err := streamsql.Execute(rsql) + assert.Nil(t, err, "复杂嵌套字段WHERE SQL应该能够执行") + + require.NoError(t, err, "复杂嵌套字段WHERE条件不应该出错") + + strm := streamsql.stream + + // 创建结果接收通道 + resultChan := make(chan interface{}, 10) + + // 添加结果接收器 + strm.AddSink(func(result []map[string]interface{}) { + resultChan <- result + }) + + // 添加测试数据:一条满足所有条件,一条不满足 + testData1 := map[string]interface{}{ + "device": map[string]interface{}{ + "info": map[string]interface{}{ + "name": "传感器A", + "status": "active", // 满足条件 + }, + "location": map[string]interface{}{ + "building": "A栋", // 满足条件 + }, + }, + "sensor": map[string]interface{}{ + "data": map[string]interface{}{ + "temperature": 30.0, // 满足条件 > 25 + }, + }, + } + + testData2 := map[string]interface{}{ + "device": map[string]interface{}{ + "info": map[string]interface{}{ + "name": "传感器B", + "status": "inactive", // 不满足条件 + }, + "location": map[string]interface{}{ + "building": "B栋", // 不满足条件 + }, + }, + "sensor": map[string]interface{}{ + "data": map[string]interface{}{ + "temperature": 20.0, // 不满足条件 <= 25 + }, + }, + } + + // 发送数据 + strm.Emit(testData1) + strm.Emit(testData2) + + // 等待结果 + var results []interface{} + timeout := time.After(3 * time.Second) + done := false + + for !done { + select { + case result := <-resultChan: + results = append(results, result) + if len(results) >= 1 { + done = true + } + case <-timeout: + done = true + } + } + + // 验证结果 + assert.Greater(t, len(results), 0, "复杂WHERE条件应该返回结果") + + for _, result := range results { + resultSlice, ok := result.([]map[string]interface{}) + require.True(t, ok, "结果应该是[]map[string]interface{}类型") + + for _, item := range resultSlice { + // 验证通过过滤的数据确实满足所有条件 + device, deviceOk := item["device"].(map[string]interface{}) + assert.True(t, deviceOk, "device字段应该存在且为map类型") + + info, infoOk := device["info"].(map[string]interface{}) + assert.True(t, infoOk, "device.info字段应该存在且为map类型") + + status, statusOk := info["status"].(string) + assert.True(t, statusOk, "device.info.status字段应该存在且为string类型") + assert.Equal(t, "active", status, "status应该是active") + + location, locationOk := device["location"].(map[string]interface{}) + assert.True(t, locationOk, "device.location字段应该存在且为map类型") + + building, buildingOk := location["building"].(string) + assert.True(t, buildingOk, "device.location.building字段应该存在且为string类型") + assert.Equal(t, "A栋", building, "building应该是A栋") + + sensor, sensorOk := item["sensor"].(map[string]interface{}) + assert.True(t, sensorOk, "sensor字段应该存在且为map类型") + + data, dataOk := sensor["data"].(map[string]interface{}) + assert.True(t, dataOk, "sensor.data字段应该存在且为map类型") + + temp, tempOk := data["temperature"].(float64) + assert.True(t, tempOk, "sensor.data.temperature字段应该存在且为float64类型") + assert.Greater(t, temp, 25.0, "temperature应该大于25") + } + } + }) +} \ No newline at end of file diff --git a/streamsql_plugin_test.go b/streamsql_plugin_test.go index 0764f49..c95bc6b 100644 --- a/streamsql_plugin_test.go +++ b/streamsql_plugin_test.go @@ -94,7 +94,7 @@ func testStringFunctionsOnly(t *testing.T) { assert.NoError(t, err) resultChan := make(chan interface{}, 10) - streamsql.Stream().AddSink(func(result interface{}) { + streamsql.Stream().AddSink(func(result []map[string]interface{}) { resultChan <- result }) @@ -140,7 +140,7 @@ func testConversionFunctionsOnly(t *testing.T) { assert.NoError(t, err) resultChan := make(chan interface{}, 10) - streamsql.Stream().AddSink(func(result interface{}) { + streamsql.Stream().AddSink(func(result []map[string]interface{}) { resultChan <- result }) @@ -186,18 +186,18 @@ func testMathFunctionsInAggregate(t *testing.T) { assert.NoError(t, err) resultChan := make(chan interface{}, 10) - streamsql.Stream().AddSink(func(result interface{}) { + streamsql.Stream().AddSink(func(result []map[string]interface{}) { resultChan <- result }) // 添加测试数据 - testData := []interface{}{ - map[string]interface{}{ + testData := []map[string]interface{}{ + { "department": "sales", "sales": 8000.0, "commission_rate": 3.0, }, - map[string]interface{}{ + { "department": "sales", "sales": 12000.0, "commission_rate": 4.0, @@ -265,7 +265,7 @@ func TestRuntimeFunctionManagement(t *testing.T) { assert.NoError(t, err) resultChan := make(chan interface{}, 10) - streamsql.Stream().AddSink(func(result interface{}) { + streamsql.Stream().AddSink(func(result []map[string]interface{}) { resultChan <- result }) @@ -380,7 +380,7 @@ func TestCompleteSQLIntegration(t *testing.T) { assert.NoError(t, err) resultChan := make(chan interface{}, 10) - streamsql.Stream().AddSink(func(result interface{}) { + streamsql.Stream().AddSink(func(result []map[string]interface{}) { resultChan <- result }) diff --git a/streamsql_quoted_support_test.go b/streamsql_quoted_support_test.go index 24617fa..db8e2f0 100644 --- a/streamsql_quoted_support_test.go +++ b/streamsql_quoted_support_test.go @@ -15,7 +15,7 @@ import ( type testCase struct { name string sql string - testData []interface{} + testData []map[string]interface{} expectedLen int validator func(t *testing.T, results []map[string]interface{}) } @@ -32,7 +32,7 @@ func executeTestCase(t *testing.T, streamsql *Streamsql, tc testCase) { var results []map[string]interface{} var resultsMutex sync.Mutex - strm.AddSink(func(result interface{}) { + strm.AddSink(func(result []map[string]interface{}) { select { case resultChan <- result: default: @@ -97,7 +97,7 @@ func executeAggregationTestCase(t *testing.T, streamsql *Streamsql, tc testCase) // 创建结果接收通道 resultChan := make(chan interface{}, 10) - strm.AddSink(func(result interface{}) { + strm.AddSink(func(result []map[string]interface{}) { select { case resultChan <- result: default: @@ -136,7 +136,7 @@ func executeFunctionTestCase(t *testing.T, streamsql *Streamsql, tc testCase) { // 创建结果接收通道 resultChan := make(chan interface{}, 10) - strm.AddSink(func(result interface{}) { + strm.AddSink(func(result []map[string]interface{}) { select { case resultChan <- result: default: @@ -173,10 +173,10 @@ func TestQuotedIdentifiersAndStringLiterals(t *testing.T) { defer streamsql.Stop() // 通用测试数据 - standardTestData := []interface{}{ - map[string]interface{}{"deviceId": "sensor001", "deviceType": "temperature"}, - map[string]interface{}{"deviceId": "device002", "deviceType": "humidity"}, - map[string]interface{}{"deviceId": "sensor003", "deviceType": "pressure"}, + standardTestData := []map[string]interface{}{ + {"deviceId": "sensor001", "deviceType": "temperature"}, + {"deviceId": "device002", "deviceType": "humidity"}, + {"deviceId": "sensor003", "deviceType": "pressure"}, } // 定义测试用例 @@ -239,7 +239,7 @@ func TestQuotedIdentifiersAndStringLiterals(t *testing.T) { { name: "字符串常量一致性验证", sql: `SELECT 'single_quote' as test1, "double_quote" as test2 FROM stream LIMIT 1`, - testData: []interface{}{map[string]interface{}{"deviceId": "test001", "deviceType": "test"}}, + testData: []map[string]interface{}{{"deviceId": "test001", "deviceType": "test"}}, expectedLen: 1, validator: func(t *testing.T, results []map[string]interface{}) { if len(results) > 0 { @@ -263,10 +263,10 @@ func TestStringConstantExpressions(t *testing.T) { defer streamsql.Stop() // 通用测试数据 - testData := []interface{}{ - map[string]interface{}{"deviceId": "sensor001", "deviceType": "temperature"}, - map[string]interface{}{"deviceId": "device002", "deviceType": "humidity"}, - map[string]interface{}{"deviceId": "sensor003", "deviceType": "pressure"}, + testData := []map[string]interface{}{ + {"deviceId": "sensor001", "deviceType": "temperature"}, + {"deviceId": "device002", "deviceType": "humidity"}, + {"deviceId": "sensor003", "deviceType": "pressure"}, } // 字符串常量验证函数 @@ -309,10 +309,10 @@ func TestAggregationWithQuotedIdentifiers(t *testing.T) { defer streamsql.Stop() // 聚合测试数据 - aggregationTestData := []interface{}{ - map[string]interface{}{"deviceId": "sensor001", "temperature": 25.5}, - map[string]interface{}{"deviceId": "sensor001", "temperature": 26.0}, - map[string]interface{}{"deviceId": "sensor002", "temperature": 30.0}, + aggregationTestData := []map[string]interface{}{ + {"deviceId": "sensor001", "temperature": 25.5}, + {"deviceId": "sensor001", "temperature": 26.0}, + {"deviceId": "sensor002", "temperature": 30.0}, } // 聚合结果验证函数 @@ -359,7 +359,7 @@ func TestCustomFunctionWithQuotedIdentifiers(t *testing.T) { { name: "函数参数:字段值vs字符串常量", sql: "SELECT deviceId, func01(temperature) as squared_temp, func02('temperature') as string_length FROM stream WHERE deviceId = 'sensor001'", - testData: []interface{}{map[string]interface{}{"deviceId": "sensor001", "temperature": 5.0}, map[string]interface{}{"deviceId": "sensor002", "temperature": 10.0}}, + testData: []map[string]interface{}{{"deviceId": "sensor001", "temperature": 5.0}, {"deviceId": "sensor002", "temperature": 10.0}}, validator: func(t *testing.T, results []map[string]interface{}) { resultSlice := results assert.Len(t, resultSlice, 1) @@ -372,7 +372,7 @@ func TestCustomFunctionWithQuotedIdentifiers(t *testing.T) { { name: "反引号标识符作为函数参数", sql: "SELECT deviceId, func01(temperature) as squared_temp, get_type(deviceId) as device_type FROM stream WHERE deviceId = 'sensor001'", - testData: []interface{}{map[string]interface{}{"deviceId": "sensor001", "temperature": 6.0}, map[string]interface{}{"deviceId": "sensor002", "temperature": 8.0}}, + testData: []map[string]interface{}{{"deviceId": "sensor001", "temperature": 6.0}, {"deviceId": "sensor002", "temperature": 8.0}}, validator: func(t *testing.T, results []map[string]interface{}) { resultSlice := results assert.Len(t, resultSlice, 1) @@ -385,7 +385,7 @@ func TestCustomFunctionWithQuotedIdentifiers(t *testing.T) { { name: "混合使用字段值和字符串常量", sql: `SELECT deviceId, func01(temperature) as field_result, func02("constant_string") as const_result, get_type('literal') as literal_type FROM stream LIMIT 1`, - testData: []interface{}{map[string]interface{}{"deviceId": "test001", "temperature": 7.0}}, + testData: []map[string]interface{}{{"deviceId": "test001", "temperature": 7.0}}, validator: func(t *testing.T, results []map[string]interface{}) { resultSlice := results assert.Len(t, resultSlice, 1) diff --git a/streamsql_sync_sink_test.go b/streamsql_sync_sink_test.go index ba4121a..9db4bd2 100644 --- a/streamsql_sync_sink_test.go +++ b/streamsql_sync_sink_test.go @@ -44,7 +44,7 @@ func TestEmitSyncWithAddSink(t *testing.T) { var sinkCallCount int32 var sinkResults []interface{} var sinkResultsMux sync.Mutex // 保护sinkResults访问 - ssql.AddSink(func(result interface{}) { + ssql.AddSink(func(result []map[string]interface{}) { atomic.AddInt32(&sinkCallCount, 1) sinkResultsMux.Lock() sinkResults = append(sinkResults, result) @@ -58,7 +58,7 @@ func TestEmitSyncWithAddSink(t *testing.T) { {"temperature": 30.0, "humidity": 80.0}, // 符合条件 } - var syncResults []interface{} + var syncResults []map[string]interface{} // 处理测试数据 for _, data := range testData { @@ -98,25 +98,24 @@ func TestEmitSyncWithAddSink(t *testing.T) { // 收集同步结果 for _, result := range syncResults { - if syncResult, ok := result.(map[string]interface{}); ok { - syncTemperatures = append(syncTemperatures, syncResult["temperature"].(float64)) - syncHumidities = append(syncHumidities, syncResult["humidity"].(float64)) + syncResult := result + syncTemperatures = append(syncTemperatures, syncResult["temperature"].(float64)) + syncHumidities = append(syncHumidities, syncResult["humidity"].(float64)) - // 验证字符串常量字段 - assert.Equal(t, "normal", syncResult["status"], "status字段应该是常量'normal'") - assert.Equal(t, "sensor_data", syncResult["data_type"], "data_type字段应该是常量'sensor_data'") + // 验证字符串常量字段 + assert.Equal(t, "normal", syncResult["status"], "status字段应该是常量'normal'") + assert.Equal(t, "sensor_data", syncResult["data_type"], "data_type字段应该是常量'sensor_data'") - // 验证反引号字段的数学运算 - expectedFahrenheit := syncResult["temperature"].(float64)*1.8 + 32 - assert.InDelta(t, expectedFahrenheit, syncResult["temp_fahrenheit"].(float64), 0.01, "华氏温度转换应该正确") + // 验证反引号字段的数学运算 + expectedFahrenheit := syncResult["temperature"].(float64)*1.8 + 32 + assert.InDelta(t, expectedFahrenheit, syncResult["temp_fahrenheit"].(float64), 0.01, "华氏温度转换应该正确") - // 验证结果包含所有预期字段 - assert.Contains(t, syncResult, "temperature", "应该包含temperature字段") - assert.Contains(t, syncResult, "humidity", "应该包含humidity字段") - assert.Contains(t, syncResult, "temp_fahrenheit", "应该包含temp_fahrenheit字段") - assert.Contains(t, syncResult, "status", "应该包含status字段") - assert.Contains(t, syncResult, "data_type", "应该包含data_type字段") - } + // 验证结果包含所有预期字段 + assert.Contains(t, syncResult, "temperature", "应该包含temperature字段") + assert.Contains(t, syncResult, "humidity", "应该包含humidity字段") + assert.Contains(t, syncResult, "temp_fahrenheit", "应该包含temp_fahrenheit字段") + assert.Contains(t, syncResult, "status", "应该包含status字段") + assert.Contains(t, syncResult, "data_type", "应该包含data_type字段") } // 收集异步结果 @@ -173,15 +172,15 @@ func TestEmitSyncWithAddSink(t *testing.T) { // 添加多个AddSink回调,使用原子操作确保线程安全 var sink1Count, sink2Count, sink3Count int32 - ssql.AddSink(func(result interface{}) { + ssql.AddSink(func(result []map[string]interface{}) { atomic.AddInt32(&sink1Count, 1) }) - ssql.AddSink(func(result interface{}) { + ssql.AddSink(func(result []map[string]interface{}) { atomic.AddInt32(&sink2Count, 1) }) - ssql.AddSink(func(result interface{}) { + ssql.AddSink(func(result []map[string]interface{}) { atomic.AddInt32(&sink3Count, 1) }) @@ -211,7 +210,7 @@ func TestEmitSyncWithAddSink(t *testing.T) { // 添加AddSink回调 var sinkCallCount int32 - ssql.AddSink(func(result interface{}) { + ssql.AddSink(func(result []map[string]interface{}) { atomic.AddInt32(&sinkCallCount, 1) }) @@ -248,17 +247,15 @@ func TestEmitSyncWithAddSink(t *testing.T) { result, err := ssql.EmitSync(testData) require.NoError(t, err) require.NotNil(t, result) + syncResult := result + // 验证反引号字段 + assert.Equal(t, 25.5, syncResult["temp"], "温度字段应该正确") + assert.Equal(t, 65.0, syncResult["humidity"], "湿度字段应该正确") - if syncResult, ok := result.(map[string]interface{}); ok { - // 验证反引号字段 - assert.Equal(t, 25.5, syncResult["temp"], "温度字段应该正确") - assert.Equal(t, 65.0, syncResult["humidity"], "湿度字段应该正确") - - // 验证字符串常量字段 - assert.Equal(t, "celsius", syncResult["unit"], "单位应该是celsius") - assert.Equal(t, "high", syncResult["level"], "级别应该是high") - assert.Equal(t, "percent", syncResult["humidity_unit"], "湿度单位应该是percent") - } + // 验证字符串常量字段 + assert.Equal(t, "celsius", syncResult["unit"], "单位应该是celsius") + assert.Equal(t, "high", syncResult["level"], "级别应该是high") + assert.Equal(t, "percent", syncResult["humidity_unit"], "湿度单位应该是percent") }) } @@ -273,7 +270,7 @@ func TestEmitSyncPerformance(t *testing.T) { // 添加AddSink回调,使用原子操作确保线程安全 var sinkCallCount int32 - ssql.AddSink(func(result interface{}) { + ssql.AddSink(func(result []map[string]interface{}) { atomic.AddInt32(&sinkCallCount, 1) }) diff --git a/streamsql_table_print_test.go b/streamsql_table_print_test.go index 6aaf0d1..7087b41 100644 --- a/streamsql_table_print_test.go +++ b/streamsql_table_print_test.go @@ -1,55 +1,45 @@ -package streamsql - -import ( - "testing" - "time" - - "github.com/stretchr/testify/assert" -) - -// TestPrintTable 测试PrintTable方法的基本功能 -func TestPrintTable(t *testing.T) { - // 创建StreamSQL实例并测试PrintTable - ssql := New() - err := ssql.Execute("SELECT device, AVG(temperature) as avg_temp FROM stream GROUP BY device, TumblingWindow('2s')") - assert.NoError(t, err) - - // 使用PrintTable方法(不验证输出内容,只确保不会panic) - assert.NotPanics(t, func() { - ssql.PrintTable() - }, "PrintTable方法不应该panic") - - // 发送测试数据 - testData := []map[string]interface{}{ - {"device": "sensor1", "temperature": 25.0}, - {"device": "sensor2", "temperature": 30.0}, - } - - for _, data := range testData { - ssql.Emit(data) - } - - // 等待窗口触发 - time.Sleep(3 * time.Second) -} - -// TestPrintTableFormat 测试printTableFormat方法处理不同数据类型 -func TestPrintTableFormat(t *testing.T) { - ssql := New() - - // 测试不同类型的数据,确保不会panic - assert.NotPanics(t, func() { - // 测试空切片 - ssql.printTableFormat([]map[string]interface{}{}) - }, "空切片不应该panic") - - assert.NotPanics(t, func() { - // 测试单个map - ssql.printTableFormat(map[string]interface{}{"key": "value"}) - }, "单个map不应该panic") - - assert.NotPanics(t, func() { - // 测试其他类型 - ssql.printTableFormat("string data") - }, "字符串数据不应该panic") -} \ No newline at end of file +package streamsql + +import ( + "testing" + "time" + + "github.com/stretchr/testify/assert" +) + +// TestPrintTable 测试PrintTable方法的基本功能 +func TestPrintTable(t *testing.T) { + // 创建StreamSQL实例并测试PrintTable + ssql := New() + err := ssql.Execute("SELECT device, AVG(temperature) as avg_temp FROM stream GROUP BY device, TumblingWindow('2s')") + assert.NoError(t, err) + + // 使用PrintTable方法(不验证输出内容,只确保不会panic) + assert.NotPanics(t, func() { + ssql.PrintTable() + }, "PrintTable方法不应该panic") + + // 发送测试数据 + testData := []map[string]interface{}{ + {"device": "sensor1", "temperature": 25.0}, + {"device": "sensor2", "temperature": 30.0}, + } + + for _, data := range testData { + ssql.Emit(data) + } + + // 等待窗口触发 + time.Sleep(3 * time.Second) +} + +// TestPrintTableFormat 测试printTableFormat方法处理不同数据类型 +func TestPrintTableFormat(t *testing.T) { + ssql := New() + + // 测试不同类型的数据,确保不会panic + assert.NotPanics(t, func() { + // 测试空切片 + ssql.printTableFormat([]map[string]interface{}{}) + }, "空切片不应该panic") +} diff --git a/streamsql_test.go b/streamsql_test.go index c36e374..3d1f060 100644 --- a/streamsql_test.go +++ b/streamsql_test.go @@ -85,7 +85,7 @@ func TestStreamData(t *testing.T) { resultChan := make(chan interface{}) // 添加计算结果回调函数(Sink) // 当窗口触发计算时,结果会通过这个回调函数输出 - ssql.stream.AddSink(func(result interface{}) { + ssql.stream.AddSink(func(result []map[string]interface{}) { resultChan <- result }) @@ -124,10 +124,10 @@ func TestStreamsql(t *testing.T) { assert.Nil(t, err) strm := streamsql.stream baseTime := time.Date(2025, 4, 7, 16, 46, 0, 0, time.UTC) - testData := []interface{}{ - map[string]interface{}{"device": "aa", "temperature": 25.0, "humidity": 60, "Ts": baseTime}, - map[string]interface{}{"device": "aa", "temperature": 30.0, "humidity": 55, "Ts": baseTime.Add(1 * time.Second)}, - map[string]interface{}{"device": "bb", "temperature": 22.0, "humidity": 70, "Ts": baseTime}, + testData := []map[string]interface{}{ + {"device": "aa", "temperature": 25.0, "humidity": 60, "Ts": baseTime}, + {"device": "aa", "temperature": 30.0, "humidity": 55, "Ts": baseTime.Add(1 * time.Second)}, + {"device": "bb", "temperature": 22.0, "humidity": 70, "Ts": baseTime}, } for _, data := range testData { @@ -135,7 +135,7 @@ func TestStreamsql(t *testing.T) { } // 捕获结果 resultChan := make(chan interface{}) - strm.AddSink(func(result interface{}) { + strm.AddSink(func(result []map[string]interface{}) { resultChan <- result }) @@ -194,10 +194,10 @@ func TestStreamsqlWithoutGroupBy(t *testing.T) { assert.Nil(t, err) strm := streamsql.stream baseTime := time.Date(2025, 4, 7, 16, 46, 0, 0, time.UTC) - testData := []interface{}{ - map[string]interface{}{"device": "aa", "temperature": 25.0, "humidity": 60, "Ts": baseTime}, - map[string]interface{}{"device": "aa", "temperature": 30.0, "humidity": 55, "Ts": baseTime.Add(1 * time.Second)}, - map[string]interface{}{"device": "bb", "temperature": 22.0, "humidity": 70, "Ts": baseTime}, + testData := []map[string]interface{}{ + {"device": "aa", "temperature": 25.0, "humidity": 60, "Ts": baseTime}, + {"device": "aa", "temperature": 30.0, "humidity": 55, "Ts": baseTime.Add(1 * time.Second)}, + {"device": "bb", "temperature": 22.0, "humidity": 70, "Ts": baseTime}, } for _, data := range testData { @@ -205,7 +205,7 @@ func TestStreamsqlWithoutGroupBy(t *testing.T) { } // 捕获结果 resultChan := make(chan interface{}) - strm.AddSink(func(result interface{}) { + strm.AddSink(func(result []map[string]interface{}) { resultChan <- result }) @@ -261,12 +261,12 @@ func TestStreamsqlDistinct(t *testing.T) { baseTime := time.Date(2025, 4, 7, 16, 46, 0, 0, time.UTC) // 添加测试数据,包含重复的设备数据 - testData := []interface{}{ - map[string]interface{}{"device": "aa", "temperature": 25.0, "Ts": baseTime}, - map[string]interface{}{"device": "aa", "temperature": 35.0, "Ts": baseTime}, // 相同设备,不同温度 - map[string]interface{}{"device": "bb", "temperature": 22.0, "Ts": baseTime}, - map[string]interface{}{"device": "bb", "temperature": 28.0, "Ts": baseTime}, // 相同设备,不同温度 - map[string]interface{}{"device": "cc", "temperature": 30.0, "Ts": baseTime}, + testData := []map[string]interface{}{ + {"device": "aa", "temperature": 25.0, "Ts": baseTime}, + {"device": "aa", "temperature": 35.0, "Ts": baseTime}, // 相同设备,不同温度 + {"device": "bb", "temperature": 22.0, "Ts": baseTime}, + {"device": "bb", "temperature": 28.0, "Ts": baseTime}, // 相同设备,不同温度 + {"device": "cc", "temperature": 30.0, "Ts": baseTime}, } // 添加数据 @@ -279,7 +279,7 @@ func TestStreamsqlDistinct(t *testing.T) { resultChan := make(chan interface{}, 10) // 添加结果回调 - strm.AddSink(func(result interface{}) { + strm.AddSink(func(result []map[string]interface{}) { //fmt.Printf("接收到结果: %v\n", result) resultChan <- result }) @@ -359,16 +359,16 @@ func TestStreamsqlLimit(t *testing.T) { resultChan := make(chan interface{}, 10) // 添加结果接收器 - strm.AddSink(func(result interface{}) { + strm.AddSink(func(result []map[string]interface{}) { resultChan <- result }) // 添加测试数据 - testData := []interface{}{ - map[string]interface{}{"device": "aa", "temperature": 25.0}, - map[string]interface{}{"device": "bb", "temperature": 22.0}, - map[string]interface{}{"device": "cc", "temperature": 30.0}, - map[string]interface{}{"device": "dd", "temperature": 28.0}, + testData := []map[string]interface{}{ + {"device": "aa", "temperature": 25.0}, + {"device": "bb", "temperature": 22.0}, + {"device": "cc", "temperature": 30.0}, + {"device": "dd", "temperature": 28.0}, } // 实时验证:添加一条数据,立即验证一条结果 @@ -420,20 +420,20 @@ func TestStreamsqlLimit(t *testing.T) { resultChan := make(chan interface{}, 10) // 添加结果回调 - strm.AddSink(func(result interface{}) { + strm.AddSink(func(result []map[string]interface{}) { resultChan <- result }) // 添加测试数据 - 多个设备的温度数据 - testData := []interface{}{ - map[string]interface{}{"device": "sensor1", "temperature": 20.0}, - map[string]interface{}{"device": "sensor1", "temperature": 22.0}, - map[string]interface{}{"device": "sensor2", "temperature": 25.0}, - map[string]interface{}{"device": "sensor2", "temperature": 27.0}, - map[string]interface{}{"device": "sensor3", "temperature": 30.0}, - map[string]interface{}{"device": "sensor3", "temperature": 32.0}, - map[string]interface{}{"device": "sensor4", "temperature": 35.0}, - map[string]interface{}{"device": "sensor4", "temperature": 37.0}, + testData := []map[string]interface{}{ + {"device": "sensor1", "temperature": 20.0}, + {"device": "sensor1", "temperature": 22.0}, + {"device": "sensor2", "temperature": 25.0}, + {"device": "sensor2", "temperature": 27.0}, + {"device": "sensor3", "temperature": 30.0}, + {"device": "sensor3", "temperature": 32.0}, + {"device": "sensor4", "temperature": 35.0}, + {"device": "sensor4", "temperature": 37.0}, } // 添加数据 @@ -498,17 +498,17 @@ func TestStreamsqlLimit(t *testing.T) { resultChan := make(chan interface{}, 10) // 添加结果回调 - strm.AddSink(func(result interface{}) { + strm.AddSink(func(result []map[string]interface{}) { resultChan <- result }) // 添加测试数据 - 5个设备的数据 - testData := []interface{}{ - map[string]interface{}{"device": "dev1", "temperature": 20.0}, - map[string]interface{}{"device": "dev2", "temperature": 25.0}, - map[string]interface{}{"device": "dev3", "temperature": 30.0}, - map[string]interface{}{"device": "dev4", "temperature": 35.0}, - map[string]interface{}{"device": "dev5", "temperature": 40.0}, + testData := []map[string]interface{}{ + {"device": "dev1", "temperature": 20.0}, + {"device": "dev2", "temperature": 25.0}, + {"device": "dev3", "temperature": 30.0}, + {"device": "dev4", "temperature": 35.0}, + {"device": "dev5", "temperature": 40.0}, } // 添加数据 @@ -568,20 +568,20 @@ func TestStreamsqlLimit(t *testing.T) { resultChan := make(chan interface{}, 10) // 添加结果回调 - strm.AddSink(func(result interface{}) { + strm.AddSink(func(result []map[string]interface{}) { resultChan <- result }) // 添加测试数据 - 设计一些平均温度大于25的设备 - testData := []interface{}{ - map[string]interface{}{"device": "cold_sensor", "temperature": 15.0}, - map[string]interface{}{"device": "cold_sensor", "temperature": 18.0}, // 平均16.5,不满足条件 - map[string]interface{}{"device": "warm_sensor1", "temperature": 26.0}, - map[string]interface{}{"device": "warm_sensor1", "temperature": 28.0}, // 平均27,满足条件 - map[string]interface{}{"device": "warm_sensor2", "temperature": 30.0}, - map[string]interface{}{"device": "warm_sensor2", "temperature": 32.0}, // 平均31,满足条件 - map[string]interface{}{"device": "warm_sensor3", "temperature": 35.0}, - map[string]interface{}{"device": "warm_sensor3", "temperature": 37.0}, // 平均36,满足条件 + testData := []map[string]interface{}{ + {"device": "cold_sensor", "temperature": 15.0}, + {"device": "cold_sensor", "temperature": 18.0}, // 平均16.5,不满足条件 + {"device": "warm_sensor1", "temperature": 26.0}, + {"device": "warm_sensor1", "temperature": 28.0}, // 平均27,满足条件 + {"device": "warm_sensor2", "temperature": 30.0}, + {"device": "warm_sensor2", "temperature": 32.0}, // 平均31,满足条件 + {"device": "warm_sensor3", "temperature": 35.0}, + {"device": "warm_sensor3", "temperature": 37.0}, // 平均36,满足条件 } // 添加数据 @@ -644,14 +644,14 @@ func TestSimpleQuery(t *testing.T) { resultChan := make(chan interface{}, 10) // 添加sink - strm.stream.AddSink(func(result interface{}) { + strm.stream.AddSink(func(result []map[string]interface{}) { //fmt.Printf("接收到结果: %v\n", result) resultChan <- result }) //添加数据 - testData := []interface{}{ - map[string]interface{}{"device": "test-device", "temperature": 25.5}, + testData := []map[string]interface{}{ + {"device": "test-device", "temperature": 25.5}, } // 发送数据 @@ -697,18 +697,18 @@ func TestHavingClause(t *testing.T) { resultChan := make(chan interface{}, 10) // 添加结果回调 - strm.AddSink(func(result interface{}) { + strm.AddSink(func(result []map[string]interface{}) { //fmt.Printf("接收到结果: %v\n", result) resultChan <- result }) // 添加测试数据,确保有不同的聚合结果 - testData := []interface{}{ - map[string]interface{}{"device": "dev1", "temperature": 20.0}, - map[string]interface{}{"device": "dev1", "temperature": 22.0}, - map[string]interface{}{"device": "dev2", "temperature": 26.0}, - map[string]interface{}{"device": "dev2", "temperature": 28.0}, - map[string]interface{}{"device": "dev3", "temperature": 30.0}, + testData := []map[string]interface{}{ + {"device": "dev1", "temperature": 20.0}, + {"device": "dev1", "temperature": 22.0}, + {"device": "dev2", "temperature": 26.0}, + {"device": "dev2", "temperature": 28.0}, + {"device": "dev3", "temperature": 30.0}, } // 添加数据 @@ -765,7 +765,7 @@ func TestSessionWindow(t *testing.T) { resultChan := make(chan interface{}, 10) // 添加结果回调 - strm.AddSink(func(result interface{}) { + strm.AddSink(func(result []map[string]interface{}) { //fmt.Printf("接收到结果: %v\n", result) resultChan <- result }) @@ -774,7 +774,7 @@ func TestSessionWindow(t *testing.T) { // 添加测试数据 - 两个设备,不同的时间 testData := []struct { - data interface{} + data map[string]interface{} wait time.Duration }{ // 第一组数据 - device1 @@ -879,11 +879,11 @@ func TestExpressionInAggregation(t *testing.T) { baseTime := time.Date(2025, 4, 7, 16, 46, 0, 0, time.UTC) // 添加测试数据,温度使用摄氏度 - testData := []interface{}{ - map[string]interface{}{"device": "aa", "temperature": 0.0, "Ts": baseTime}, // 华氏度应为 32 - map[string]interface{}{"device": "aa", "temperature": 100.0, "Ts": baseTime}, // 华氏度应为 212 - map[string]interface{}{"device": "bb", "temperature": 20.0, "Ts": baseTime}, // 华氏度应为 68 - map[string]interface{}{"device": "bb", "temperature": 30.0, "Ts": baseTime}, // 华氏度应为 86 + testData := []map[string]interface{}{ + {"device": "aa", "temperature": 0.0, "Ts": baseTime}, // 华氏度应为 32 + {"device": "aa", "temperature": 100.0, "Ts": baseTime}, // 华氏度应为 212 + {"device": "bb", "temperature": 20.0, "Ts": baseTime}, // 华氏度应为 68 + {"device": "bb", "temperature": 30.0, "Ts": baseTime}, // 华氏度应为 86 } // 添加数据 @@ -896,7 +896,7 @@ func TestExpressionInAggregation(t *testing.T) { resultChan := make(chan interface{}, 10) // 添加结果回调 - strm.AddSink(func(result interface{}) { + strm.AddSink(func(result []map[string]interface{}) { //fmt.Printf("接收到结果: %v\n", result) resultChan <- result }) @@ -964,11 +964,11 @@ func TestAdvancedFunctionsInSQL(t *testing.T) { baseTime := time.Date(2025, 4, 7, 16, 46, 0, 0, time.UTC) // 添加测试数据 - testData := []interface{}{ - map[string]interface{}{"device": "sensor1", "temperature": 15.0, "Ts": baseTime}, // abs(15-20) = 5 - map[string]interface{}{"device": "sensor1", "temperature": 25.0, "Ts": baseTime}, // abs(25-20) = 5 - map[string]interface{}{"device": "sensor2", "temperature": 18.0, "Ts": baseTime}, // abs(18-20) = 2 - map[string]interface{}{"device": "sensor2", "temperature": 22.0, "Ts": baseTime}, // abs(22-20) = 2 + testData := []map[string]interface{}{ + {"device": "sensor1", "temperature": 15.0, "Ts": baseTime}, // abs(15-20) = 5 + {"device": "sensor1", "temperature": 25.0, "Ts": baseTime}, // abs(25-20) = 5 + {"device": "sensor2", "temperature": 18.0, "Ts": baseTime}, // abs(18-20) = 2 + {"device": "sensor2", "temperature": 22.0, "Ts": baseTime}, // abs(22-20) = 2 } // 添加数据 @@ -981,7 +981,7 @@ func TestAdvancedFunctionsInSQL(t *testing.T) { resultChan := make(chan interface{}, 10) // 添加结果回调 - strm.AddSink(func(result interface{}) { + strm.AddSink(func(result []map[string]interface{}) { //fmt.Printf("接收到结果: %v\n", result) resultChan <- result }) @@ -1063,11 +1063,11 @@ func TestCustomFunctionInSQL(t *testing.T) { baseTime := time.Date(2025, 4, 7, 16, 46, 0, 0, time.UTC) // 添加测试数据(华氏度) - testData := []interface{}{ - map[string]interface{}{"device": "thermometer1", "temperature": 32.0, "Ts": baseTime}, // 0°C - map[string]interface{}{"device": "thermometer1", "temperature": 212.0, "Ts": baseTime}, // 100°C - map[string]interface{}{"device": "thermometer2", "temperature": 68.0, "Ts": baseTime}, // 20°C - map[string]interface{}{"device": "thermometer2", "temperature": 86.0, "Ts": baseTime}, // 30°C + testData := []map[string]interface{}{ + {"device": "thermometer1", "temperature": 32.0, "Ts": baseTime}, // 0°C + {"device": "thermometer1", "temperature": 212.0, "Ts": baseTime}, // 100°C + {"device": "thermometer2", "temperature": 68.0, "Ts": baseTime}, // 20°C + {"device": "thermometer2", "temperature": 86.0, "Ts": baseTime}, // 30°C } // 添加数据 @@ -1080,7 +1080,7 @@ func TestCustomFunctionInSQL(t *testing.T) { resultChan := make(chan interface{}, 10) // 添加结果回调 - strm.AddSink(func(result interface{}) { + strm.AddSink(func(result []map[string]interface{}) { //fmt.Printf("接收到结果: %v\n", result) resultChan <- result }) @@ -1148,11 +1148,11 @@ func TestNewAggregateFunctionsInSQL(t *testing.T) { baseTime := time.Date(2025, 4, 7, 16, 46, 0, 0, time.UTC) // 添加测试数据 - testData := []interface{}{ - map[string]interface{}{"device": "sensor1", "temperature": 15.0, "status": "good", "Ts": baseTime}, - map[string]interface{}{"device": "sensor1", "temperature": 25.0, "status": "ok", "Ts": baseTime}, - map[string]interface{}{"device": "sensor2", "temperature": 18.0, "status": "good", "Ts": baseTime}, - map[string]interface{}{"device": "sensor2", "temperature": 22.0, "status": "warning", "Ts": baseTime}, + testData := []map[string]interface{}{ + {"device": "sensor1", "temperature": 15.0, "status": "good", "Ts": baseTime}, + {"device": "sensor1", "temperature": 25.0, "status": "ok", "Ts": baseTime}, + {"device": "sensor2", "temperature": 18.0, "status": "good", "Ts": baseTime}, + {"device": "sensor2", "temperature": 22.0, "status": "warning", "Ts": baseTime}, } // 添加数据 @@ -1165,7 +1165,7 @@ func TestNewAggregateFunctionsInSQL(t *testing.T) { resultChan := make(chan interface{}, 10) // 添加结果回调 - strm.AddSink(func(result interface{}) { + strm.AddSink(func(result []map[string]interface{}) { //fmt.Printf("接收到结果: %v\n", result) resultChan <- result }) @@ -1257,12 +1257,12 @@ func TestStatisticalAggregateFunctionsInSQL(t *testing.T) { baseTime := time.Date(2025, 4, 7, 16, 46, 0, 0, time.UTC) // 添加测试数据 - testData := []interface{}{ - map[string]interface{}{"device": "sensor1", "temperature": 10.0, "Ts": baseTime}, - map[string]interface{}{"device": "sensor1", "temperature": 20.0, "Ts": baseTime}, - map[string]interface{}{"device": "sensor1", "temperature": 30.0, "Ts": baseTime}, - map[string]interface{}{"device": "sensor2", "temperature": 15.0, "Ts": baseTime}, - map[string]interface{}{"device": "sensor2", "temperature": 25.0, "Ts": baseTime}, + testData := []map[string]interface{}{ + {"device": "sensor1", "temperature": 10.0, "Ts": baseTime}, + {"device": "sensor1", "temperature": 20.0, "Ts": baseTime}, + {"device": "sensor1", "temperature": 30.0, "Ts": baseTime}, + {"device": "sensor2", "temperature": 15.0, "Ts": baseTime}, + {"device": "sensor2", "temperature": 25.0, "Ts": baseTime}, } // 添加数据 @@ -1275,7 +1275,7 @@ func TestStatisticalAggregateFunctionsInSQL(t *testing.T) { resultChan := make(chan interface{}, 10) // 添加结果回调 - strm.AddSink(func(result interface{}) { + strm.AddSink(func(result []map[string]interface{}) { //fmt.Printf("接收到结果: %v\n", result) resultChan <- result }) @@ -1357,14 +1357,14 @@ func TestDeduplicateAggregateInSQL(t *testing.T) { baseTime := time.Date(2025, 4, 7, 16, 46, 0, 0, time.UTC) // 添加测试数据,包含重复的状态 - testData := []interface{}{ - map[string]interface{}{"device": "sensor1", "status": "good", "Ts": baseTime}, - map[string]interface{}{"device": "sensor1", "status": "good", "Ts": baseTime}, // 重复 - map[string]interface{}{"device": "sensor1", "status": "warning", "Ts": baseTime}, - map[string]interface{}{"device": "sensor1", "status": "good", "Ts": baseTime}, // 重复 - map[string]interface{}{"device": "sensor2", "status": "error", "Ts": baseTime}, - map[string]interface{}{"device": "sensor2", "status": "error", "Ts": baseTime}, // 重复 - map[string]interface{}{"device": "sensor2", "status": "ok", "Ts": baseTime}, + testData := []map[string]interface{}{ + {"device": "sensor1", "status": "good", "Ts": baseTime}, + {"device": "sensor1", "status": "good", "Ts": baseTime}, // 重复 + {"device": "sensor1", "status": "warning", "Ts": baseTime}, + {"device": "sensor1", "status": "good", "Ts": baseTime}, // 重复 + {"device": "sensor2", "status": "error", "Ts": baseTime}, + {"device": "sensor2", "status": "error", "Ts": baseTime}, // 重复 + {"device": "sensor2", "status": "ok", "Ts": baseTime}, } // 添加数据 @@ -1377,7 +1377,7 @@ func TestDeduplicateAggregateInSQL(t *testing.T) { resultChan := make(chan interface{}, 10) // 添加结果回调 - strm.AddSink(func(result interface{}) { + strm.AddSink(func(result []map[string]interface{}) { //fmt.Printf("接收到结果: %v\n", result) resultChan <- result }) @@ -1465,16 +1465,16 @@ func TestExprAggregationFunctions(t *testing.T) { baseTime := time.Date(2025, 4, 7, 16, 46, 0, 0, time.UTC) // 添加测试数据 - testData := []interface{}{ + testData := []map[string]interface{}{ // device1的数据 - map[string]interface{}{"device": "device1", "temperature": 20.0, "humidity": 60.0, "status": "normal", "Ts": baseTime}, // 华氏度=68, 偏差=0, 和=80 - map[string]interface{}{"device": "device1", "temperature": 25.0, "humidity": 65.0, "status": "warning", "Ts": baseTime}, // 华氏度=77, 偏差=10, 和=90 - map[string]interface{}{"device": "device1", "temperature": 30.0, "humidity": 70.0, "status": "normal", "Ts": baseTime}, // 华氏度=86, 偏差=20, 和=100 + {"device": "device1", "temperature": 20.0, "humidity": 60.0, "status": "normal", "Ts": baseTime}, // 华氏度=68, 偏差=0, 和=80 + {"device": "device1", "temperature": 25.0, "humidity": 65.0, "status": "warning", "Ts": baseTime}, // 华氏度=77, 偏差=10, 和=90 + {"device": "device1", "temperature": 30.0, "humidity": 70.0, "status": "normal", "Ts": baseTime}, // 华氏度=86, 偏差=20, 和=100 // device2的数据 - map[string]interface{}{"device": "device2", "temperature": 15.0, "humidity": 55.0, "status": "error", "Ts": baseTime}, // 华氏度=59, 偏差=-10, 和=70 - map[string]interface{}{"device": "device2", "temperature": 18.0, "humidity": 58.0, "status": "normal", "Ts": baseTime}, // 华氏度=64.4, 偏差=-4, 和=76 - map[string]interface{}{"device": "device2", "temperature": 22.0, "humidity": 62.0, "status": "error", "Ts": baseTime}, // 华氏度=71.6, 偏差=4, 和=84 + {"device": "device2", "temperature": 15.0, "humidity": 55.0, "status": "error", "Ts": baseTime}, // 华氏度=59, 偏差=-10, 和=70 + {"device": "device2", "temperature": 18.0, "humidity": 58.0, "status": "normal", "Ts": baseTime}, // 华氏度=64.4, 偏差=-4, 和=76 + {"device": "device2", "temperature": 22.0, "humidity": 62.0, "status": "error", "Ts": baseTime}, // 华氏度=71.6, 偏差=4, 和=84 } // 添加数据 @@ -1487,7 +1487,7 @@ func TestExprAggregationFunctions(t *testing.T) { resultChan := make(chan interface{}, 10) // 添加结果回调 - strm.AddSink(func(result interface{}) { + strm.AddSink(func(result []map[string]interface{}) { //fmt.Printf("接收到结果: %v\n", result) resultChan <- result }) @@ -1626,12 +1626,12 @@ func TestAnalyticalFunctionsInSQL(t *testing.T) { baseTime := time.Date(2025, 4, 7, 16, 46, 0, 0, time.UTC) // 添加测试数据 - testData := []interface{}{ - map[string]interface{}{"device": "sensor1", "temperature": 20.0, "Ts": baseTime}, - map[string]interface{}{"device": "sensor1", "temperature": 25.0, "Ts": baseTime}, - map[string]interface{}{"device": "sensor1", "temperature": 25.0, "Ts": baseTime}, // 重复值,测试had_changed - map[string]interface{}{"device": "sensor2", "temperature": 18.0, "Ts": baseTime}, - map[string]interface{}{"device": "sensor2", "temperature": 22.0, "Ts": baseTime}, + testData := []map[string]interface{}{ + {"device": "sensor1", "temperature": 20.0, "Ts": baseTime}, + {"device": "sensor1", "temperature": 25.0, "Ts": baseTime}, + {"device": "sensor1", "temperature": 25.0, "Ts": baseTime}, // 重复值,测试had_changed + {"device": "sensor2", "temperature": 18.0, "Ts": baseTime}, + {"device": "sensor2", "temperature": 22.0, "Ts": baseTime}, } // 添加数据 @@ -1644,7 +1644,7 @@ func TestAnalyticalFunctionsInSQL(t *testing.T) { resultChan := make(chan interface{}, 10) // 添加结果回调 - strm.AddSink(func(result interface{}) { + strm.AddSink(func(result []map[string]interface{}) { //fmt.Printf("接收到结果: %v\n", result) resultChan <- result }) @@ -1723,11 +1723,11 @@ func TestLagFunctionInSQL(t *testing.T) { baseTime := time.Date(2025, 4, 7, 16, 46, 0, 0, time.UTC) // 添加测试数据 - 按顺序添加,测试LAG功能 - testData := []interface{}{ - map[string]interface{}{"device": "temp_sensor", "temperature": 10.0, "Ts": baseTime}, - map[string]interface{}{"device": "temp_sensor", "temperature": 15.0, "Ts": baseTime}, - map[string]interface{}{"device": "temp_sensor", "temperature": 20.0, "Ts": baseTime}, - map[string]interface{}{"device": "temp_sensor", "temperature": 25.0, "Ts": baseTime}, // 最后一个值 + testData := []map[string]interface{}{ + {"device": "temp_sensor", "temperature": 10.0, "Ts": baseTime}, + {"device": "temp_sensor", "temperature": 15.0, "Ts": baseTime}, + {"device": "temp_sensor", "temperature": 20.0, "Ts": baseTime}, + {"device": "temp_sensor", "temperature": 25.0, "Ts": baseTime}, // 最后一个值 } // 添加数据 @@ -1742,7 +1742,7 @@ func TestLagFunctionInSQL(t *testing.T) { resultChan := make(chan interface{}, 10) // 添加结果回调 - strm.AddSink(func(result interface{}) { + strm.AddSink(func(result []map[string]interface{}) { //fmt.Printf("接收到结果: %v\n", result) resultChan <- result }) @@ -1821,12 +1821,12 @@ func TestHadChangedFunctionInSQL(t *testing.T) { baseTime := time.Date(2025, 4, 7, 16, 46, 0, 0, time.UTC) // 添加测试数据 - 包含重复值和变化值 - testData := []interface{}{ - map[string]interface{}{"device": "monitor", "temperature": 20.0, "Ts": baseTime}, - map[string]interface{}{"device": "monitor", "temperature": 20.0, "Ts": baseTime}, // 相同值 - map[string]interface{}{"device": "monitor", "temperature": 25.0, "Ts": baseTime}, // 变化值 - map[string]interface{}{"device": "monitor", "temperature": 25.0, "Ts": baseTime}, // 相同值 - map[string]interface{}{"device": "monitor", "temperature": 30.0, "Ts": baseTime}, // 变化值 + testData := []map[string]interface{}{ + {"device": "monitor", "temperature": 20.0, "Ts": baseTime}, + {"device": "monitor", "temperature": 20.0, "Ts": baseTime}, // 相同值 + {"device": "monitor", "temperature": 25.0, "Ts": baseTime}, // 变化值 + {"device": "monitor", "temperature": 25.0, "Ts": baseTime}, // 相同值 + {"device": "monitor", "temperature": 30.0, "Ts": baseTime}, // 变化值 } // 添加数据 @@ -1839,7 +1839,7 @@ func TestHadChangedFunctionInSQL(t *testing.T) { resultChan := make(chan interface{}, 10) // 添加结果回调 - strm.AddSink(func(result interface{}) { + strm.AddSink(func(result []map[string]interface{}) { //fmt.Printf("接收到结果: %v\n", result) resultChan <- result }) @@ -1902,11 +1902,11 @@ func TestLatestFunctionInSQL(t *testing.T) { baseTime := time.Date(2025, 4, 7, 16, 46, 0, 0, time.UTC) // 添加测试数据 - testData := []interface{}{ - map[string]interface{}{"device": "thermometer", "temperature": 10.0, "Ts": baseTime}, - map[string]interface{}{"device": "thermometer", "temperature": 15.0, "Ts": baseTime}, - map[string]interface{}{"device": "thermometer", "temperature": 20.0, "Ts": baseTime}, - map[string]interface{}{"device": "thermometer", "temperature": 25.0, "Ts": baseTime}, // 最新值 + testData := []map[string]interface{}{ + {"device": "thermometer", "temperature": 10.0, "Ts": baseTime}, + {"device": "thermometer", "temperature": 15.0, "Ts": baseTime}, + {"device": "thermometer", "temperature": 20.0, "Ts": baseTime}, + {"device": "thermometer", "temperature": 25.0, "Ts": baseTime}, // 最新值 } // 添加数据 @@ -1919,7 +1919,7 @@ func TestLatestFunctionInSQL(t *testing.T) { resultChan := make(chan interface{}, 10) // 添加结果回调 - strm.AddSink(func(result interface{}) { + strm.AddSink(func(result []map[string]interface{}) { //fmt.Printf("接收到结果: %v\n", result) resultChan <- result }) @@ -1983,18 +1983,18 @@ func TestChangedColFunctionInSQL(t *testing.T) { baseTime := time.Date(2025, 4, 7, 16, 46, 0, 0, time.UTC) // 添加测试数据 - 使用map作为数据测试changed_col - testData := []interface{}{ - map[string]interface{}{ + testData := []map[string]interface{}{ + { "device": "datacollector", "data": map[string]interface{}{"temp": 20.0, "humidity": 60.0}, "Ts": baseTime, }, - map[string]interface{}{ + { "device": "datacollector", "data": map[string]interface{}{"temp": 25.0, "humidity": 60.0}, // temp变化 "Ts": baseTime, }, - map[string]interface{}{ + { "device": "datacollector", "data": map[string]interface{}{"temp": 25.0, "humidity": 65.0}, // humidity变化 "Ts": baseTime, @@ -2011,7 +2011,7 @@ func TestChangedColFunctionInSQL(t *testing.T) { resultChan := make(chan interface{}, 10) // 添加结果回调 - strm.AddSink(func(result interface{}) { + strm.AddSink(func(result []map[string]interface{}) { //fmt.Printf("接收到结果: %v\n", result) resultChan <- result }) @@ -2074,12 +2074,12 @@ func TestAnalyticalFunctionsIncrementalComputation(t *testing.T) { baseTime := time.Date(2025, 4, 7, 16, 46, 0, 0, time.UTC) // 添加测试数据 - testData := []interface{}{ - map[string]interface{}{"device": "sensor1", "temperature": 15.0, "status": "good", "Ts": baseTime}, - map[string]interface{}{"device": "sensor1", "temperature": 25.0, "status": "good", "Ts": baseTime}, - map[string]interface{}{"device": "sensor1", "temperature": 35.0, "status": "warning", "Ts": baseTime}, - map[string]interface{}{"device": "sensor2", "temperature": 18.0, "status": "good", "Ts": baseTime}, - map[string]interface{}{"device": "sensor2", "temperature": 22.0, "status": "ok", "Ts": baseTime}, + testData := []map[string]interface{}{ + {"device": "sensor1", "temperature": 15.0, "status": "good", "Ts": baseTime}, + {"device": "sensor1", "temperature": 25.0, "status": "good", "Ts": baseTime}, + {"device": "sensor1", "temperature": 35.0, "status": "warning", "Ts": baseTime}, + {"device": "sensor2", "temperature": 18.0, "status": "good", "Ts": baseTime}, + {"device": "sensor2", "temperature": 22.0, "status": "ok", "Ts": baseTime}, } // 添加数据 @@ -2092,7 +2092,7 @@ func TestAnalyticalFunctionsIncrementalComputation(t *testing.T) { resultChan := make(chan interface{}, 10) // 添加结果回调 - strm.AddSink(func(result interface{}) { + strm.AddSink(func(result []map[string]interface{}) { //fmt.Printf("接收到结果: %v\n", result) resultChan <- result }) @@ -2173,12 +2173,12 @@ func TestIncrementalComputationBasic(t *testing.T) { baseTime := time.Date(2025, 4, 7, 16, 46, 0, 0, time.UTC) // 添加测试数据 - testData := []interface{}{ - map[string]interface{}{"device": "sensor1", "temperature": 10.0, "Ts": baseTime}, - map[string]interface{}{"device": "sensor1", "temperature": 20.0, "Ts": baseTime}, - map[string]interface{}{"device": "sensor1", "temperature": 30.0, "Ts": baseTime}, - map[string]interface{}{"device": "sensor2", "temperature": 15.0, "Ts": baseTime}, - map[string]interface{}{"device": "sensor2", "temperature": 25.0, "Ts": baseTime}, + testData := []map[string]interface{}{ + {"device": "sensor1", "temperature": 10.0, "Ts": baseTime}, + {"device": "sensor1", "temperature": 20.0, "Ts": baseTime}, + {"device": "sensor1", "temperature": 30.0, "Ts": baseTime}, + {"device": "sensor2", "temperature": 15.0, "Ts": baseTime}, + {"device": "sensor2", "temperature": 25.0, "Ts": baseTime}, } // 添加数据 @@ -2191,7 +2191,7 @@ func TestIncrementalComputationBasic(t *testing.T) { resultChan := make(chan interface{}, 10) // 添加结果回调 - strm.AddSink(func(result interface{}) { + strm.AddSink(func(result []map[string]interface{}) { //fmt.Printf("接收到结果: %v\n", result) resultChan <- result }) @@ -2276,14 +2276,14 @@ func TestExprFunctions(t *testing.T) { resultChan := make(chan interface{}, 10) // 添加结果回调 - strm.AddSink(func(result interface{}) { + strm.AddSink(func(result []map[string]interface{}) { resultChan <- result }) // 添加测试数据 - testData := []interface{}{ - map[string]interface{}{"device": "SensorA"}, - map[string]interface{}{"device": "SensorB"}, + testData := []map[string]interface{}{ + {"device": "SensorA"}, + {"device": "SensorB"}, } // 添加数据 @@ -2357,18 +2357,18 @@ func TestExprFunctionsInAggregation(t *testing.T) { baseTime := time.Date(2025, 4, 7, 16, 46, 0, 0, time.UTC) // 添加测试数据 - testData := []interface{}{ - map[string]interface{}{"device": "sensor1", "temperature": 23.5, "Ts": baseTime}, // abs(23.5-25) = 1.5, ceil(23.5) = 24 - map[string]interface{}{"device": "sensor1", "temperature": 26.8, "Ts": baseTime}, // abs(26.8-25) = 1.8, ceil(26.8) = 27 - map[string]interface{}{"device": "sensor2", "temperature": 24.2, "Ts": baseTime}, // abs(24.2-25) = 0.8, ceil(24.2) = 25 - map[string]interface{}{"device": "sensor2", "temperature": 25.9, "Ts": baseTime}, // abs(25.9-25) = 0.9, ceil(25.9) = 26 + testData := []map[string]interface{}{ + {"device": "sensor1", "temperature": 23.5, "Ts": baseTime}, // abs(23.5-25) = 1.5, ceil(23.5) = 24 + {"device": "sensor1", "temperature": 26.8, "Ts": baseTime}, // abs(26.8-25) = 1.8, ceil(26.8) = 27 + {"device": "sensor2", "temperature": 24.2, "Ts": baseTime}, // abs(24.2-25) = 0.8, ceil(24.2) = 25 + {"device": "sensor2", "temperature": 25.9, "Ts": baseTime}, // abs(25.9-25) = 0.9, ceil(25.9) = 26 } // 创建结果接收通道 resultChan := make(chan interface{}, 10) // 添加结果回调 - strm.AddSink(func(result interface{}) { + strm.AddSink(func(result []map[string]interface{}) { resultChan <- result }) @@ -2437,15 +2437,15 @@ func TestNestedExprFunctions(t *testing.T) { resultChan := make(chan interface{}, 10) // 添加结果回调 - strm.AddSink(func(result interface{}) { + strm.AddSink(func(result []map[string]interface{}) { resultChan <- result }) // 添加测试数据 - testData := []interface{}{ - map[string]interface{}{"device": "sensor1"}, // upper -> "SENSOR1", split by "SENSOR" -> ["", "1"], len -> 2 - map[string]interface{}{"device": "sensorsensor"}, // upper -> "SENSORSENSOR", split by "SENSOR" -> ["", "", ""], len -> 3 - map[string]interface{}{"device": "device1"}, // upper -> "DEVICE1", split by "SENSOR" -> ["DEVICE1"], len -> 1 + testData := []map[string]interface{}{ + {"device": "sensor1"}, // upper -> "SENSOR1", split by "SENSOR" -> ["", "1"], len -> 2 + {"device": "sensorsensor"}, // upper -> "SENSORSENSOR", split by "SENSOR" -> ["", "", ""], len -> 3 + {"device": "device1"}, // upper -> "DEVICE1", split by "SENSOR" -> ["DEVICE1"], len -> 1 } // 添加数据 @@ -2528,14 +2528,14 @@ func TestExprFunctionsWithStreamSQLFunctions(t *testing.T) { resultChan := make(chan interface{}, 10) // 添加结果回调 - strm.AddSink(func(result interface{}) { + strm.AddSink(func(result []map[string]interface{}) { resultChan <- result }) // 添加测试数据 - testData := []interface{}{ - map[string]interface{}{"device": "sensor1"}, - map[string]interface{}{"device": "device2"}, + testData := []map[string]interface{}{ + {"device": "sensor1"}, + {"device": "device2"}, } // 添加数据 @@ -2608,7 +2608,7 @@ func TestSelectAllFeature(t *testing.T) { resultChan := make(chan interface{}, 10) // 添加结果接收器 - strm.AddSink(func(result interface{}) { + strm.AddSink(func(result []map[string]interface{}) { resultChan <- result }) @@ -2666,7 +2666,7 @@ func TestSelectAllFeature(t *testing.T) { resultChan := make(chan interface{}, 10) // 添加结果接收器 - strm.AddSink(func(result interface{}) { + strm.AddSink(func(result []map[string]interface{}) { resultChan <- result }) @@ -2750,7 +2750,7 @@ func TestSelectAllFeature(t *testing.T) { resultChan := make(chan interface{}, 10) // 添加结果接收器 - strm.AddSink(func(result interface{}) { + strm.AddSink(func(result []map[string]interface{}) { resultChan <- result }) @@ -2822,7 +2822,7 @@ func TestSelectAllFeature(t *testing.T) { resultChan := make(chan interface{}, 10) // 添加结果接收器 - strm.AddSink(func(result interface{}) { + strm.AddSink(func(result []map[string]interface{}) { resultChan <- result }) @@ -2896,7 +2896,7 @@ func TestCaseNullValueHandlingInAggregation(t *testing.T) { var results []map[string]interface{} resultChan := make(chan interface{}, 10) - ssql.AddSink(func(result interface{}) { + ssql.AddSink(func(result []map[string]interface{}) { resultChan <- result })