From 22c93fa55668754c7ff27fcb56c09ce6495f87e2 Mon Sep 17 00:00:00 2001 From: Faiz Muttaqin Date: Thu, 23 Jul 2026 23:25:57 +0700 Subject: [PATCH] docs: translate documentation and update dependencies Signed-off-by: Faiz Muttaqin --- .golangci.yml | 12 +- README.md | 2 +- aggregator/builtin.go | 6 +- aggregator/builtin_test.go | 48 +- aggregator/group_aggregator.go | 20 +- aggregator/group_aggregator_test.go | 394 ++++---- aggregator/post_aggregation.go | 70 +- aggregator/post_aggregation_test.go | 124 +-- cep/engine.go | 236 ++--- cep/engine_test.go | 114 +-- cep/eval.go | 160 +-- cep/eval_test.go | 66 +- cep/nfa.go | 36 +- cep/nfa_test.go | 20 +- cep/pattern.go | 20 +- cep/pattern_test.go | 10 +- cep/stress_test.go | 60 +- condition/condition.go | 2 +- condition/condition_test.go | 22 +- docs/CUSTOM_FUNCTIONS_GUIDE.md | 666 ++++++------ docs/FUNCTIONS.md | 404 ++++---- docs/FUNCTIONS_USAGE_GUIDE.md | 956 +++++++++--------- docs/FUNCTION_INTEGRATION.md | 784 +++++++------- docs/FUNCTION_QUICK_START.md | 656 ++++++------ docs/FUNCTION_VALIDATION.md | 150 +-- docs/NEGATIVE_NUMBER_SUPPORT.md | 400 ++++---- docs/NESTED_FIELD_ACCESS.md | 526 +++++----- docs/PLUGIN_EXAMPLE.md | 286 +++--- examples/advanced-functions/README.md | 174 ++-- examples/advanced-functions/main.go | 38 +- examples/complex-nested-access/main.go | 76 +- examples/comprehensive-test/README.md | 124 +-- examples/comprehensive-test/main.go | 92 +- examples/custom-functions-demo/README.md | 134 +-- examples/custom-functions-demo/main.go | 124 +-- examples/function-integration-demo/README.md | 110 +- examples/function-integration-demo/main.go | 22 +- examples/nested-field-examples/README.md | 402 ++++---- examples/nested-field-examples/main.go | 110 +- examples/non-aggregation/README.md | 228 ++--- examples/non-aggregation/main.go | 72 +- examples/null-comparison-examples/main.go | 60 +- examples/simple-custom-functions/README.md | 100 +- examples/simple-custom-functions/main.go | 48 +- examples/table_print_demo/main.go | 22 +- expr/case_expression_test.go | 4 +- expr/evaluator_test.go | 56 +- expr/expression_test.go | 62 +- expr/parser_test.go | 16 +- expr/tokenizer_custom_test.go | 2 +- expr/tokenizer_test.go | 40 +- expr/utils_test.go | 52 +- expr/validator_test.go | 94 +- functions/README.md | 270 ++--- functions/aggregator_adapter.go | 6 +- functions/aggregator_adapter_test.go | 42 +- functions/aggregator_interface_test.go | 84 +- functions/aggregator_types_test.go | 65 +- functions/analytic_acc.go | 46 +- functions/analytic_state.go | 38 +- functions/base.go | 4 +- functions/custom_function_test.go | 54 +- functions/expr_bridge.go | 228 ++--- functions/expr_bridge_test.go | 76 +- functions/functions_aggregation.go | 120 +-- functions/functions_aggregation_test.go | 78 +- functions/functions_analytical.go | 38 +- functions/functions_array.go | 22 +- functions/functions_array_test.go | 20 +- functions/functions_conditional.go | 38 +- functions/functions_conditional_test.go | 8 +- functions/functions_conversion.go | 46 +- functions/functions_conversion_test.go | 18 +- functions/functions_datetime.go | 52 +- functions/functions_datetime_test.go | 48 +- functions/functions_expr.go | 6 +- functions/functions_expr_test.go | 40 +- functions/functions_hash_test.go | 6 +- functions/functions_json.go | 6 +- functions/functions_json_test.go | 12 +- functions/functions_math.go | 54 +- functions/functions_math_test.go | 16 +- functions/functions_multirow.go | 44 +- functions/functions_multirow_test.go | 36 +- functions/functions_string.go | 44 +- functions/functions_string_test.go | 16 +- functions/functions_test.go | 150 ++- functions/functions_type.go | 4 +- functions/functions_type_test.go | 6 +- functions/functions_window.go | 48 +- functions/functions_window_test.go | 98 +- functions/init.go | 2 +- functions/integration_test.go | 14 +- functions/oneentry_register_test.go | 38 +- functions/registry.go | 28 +- functions/registry_test.go | 140 +-- functions/regression_test.go | 6 +- go.mod | 4 +- go.sum | 4 +- logger/logger_test.go | 128 +-- options_test.go | 46 +- rsql/analytic_extract.go | 29 +- rsql/ast.go | 252 ++--- rsql/ast_test.go | 36 +- rsql/coverage_test.go | 158 +-- rsql/error_test.go | 32 +- rsql/function_validator.go | 4 +- rsql/lexer.go | 74 +- rsql/lexer_test.go | 32 +- rsql/parser.go | 326 +++--- rsql/parser_match.go | 54 +- rsql/parser_match_pattern.go | 26 +- rsql/parser_match_test.go | 18 +- rsql/parser_test.go | 152 +-- rsql/statettl_test.go | 4 +- stream/analytic.go | 118 +-- stream/analytic_test.go | 22 +- stream/cep.go | 12 +- stream/coverage_test.go | 188 ++-- stream/expand_test.go | 6 +- stream/handler_data_test.go | 22 +- stream/handler_result_test.go | 66 +- stream/processor_data.go | 20 +- stream/processor_data_test.go | 38 +- stream/processor_field.go | 8 +- stream/processor_field_test.go | 34 +- stream/strategy_test.go | 100 +- stream/stream.go | 88 +- stream/stream_factory.go | 4 +- stream/stream_field_test.go | 22 +- stream/stream_test.go | 322 +++--- stream/stream_window_test.go | 16 +- stream/table_store.go | 10 +- stream/table_store_test.go | 10 +- streamsql.go | 4 +- streamsql_benchmark_test.go | 82 +- streamsql_stress_test.go | 2 +- streamsql_test.go | 344 +++---- test/e2e/analytic_b_test.go | 12 +- test/e2e/analytic_cdc_test.go | 54 +- test/e2e/analytic_combo_test.go | 192 ++-- test/e2e/analytic_parity_test.go | 96 +- test/e2e/analytic_runtime_test.go | 75 +- test/e2e/analytic_test.go | 46 +- test/e2e/case_test.go | 244 ++--- test/e2e/cep_doc_verify_test.go | 16 +- test/e2e/cep_stress_test.go | 56 +- test/e2e/cep_test.go | 170 ++-- test/e2e/condition_complex_test.go | 16 +- .../e2e/custom_functions_single_entry_test.go | 28 +- test/e2e/custom_functions_test.go | 142 +-- test/e2e/doc_cases_test.go | 26 +- test/e2e/error_handling_test.go | 50 +- test/e2e/evaluator_semantics_test.go | 24 +- test/e2e/function_advanced_test.go | 614 +++++------ test/e2e/function_scenarios_test.go | 60 +- test/e2e/function_test.go | 330 +++--- test/e2e/j2_panic_recover_test.go | 8 +- test/e2e/like_test.go | 114 +-- test/e2e/nested_field_test.go | 98 +- test/e2e/null_test.go | 220 ++-- test/e2e/plugin_test.go | 72 +- test/e2e/post_aggregation_test.go | 96 +- test/e2e/quoted_test.go | 98 +- test/e2e/select_test.go | 8 +- test/e2e/sink_test.go | 98 +- test/e2e/sql_check_test.go | 178 ++-- test/e2e/stress_test.go | 68 +- test/e2e/trigger_window_test.go | 8 +- test/e2e/validation_test.go | 6 +- test/e2e/window_aggregate_combo_test.go | 30 +- test/e2e/window_counting_test.go | 4 +- test/e2e/window_func_support_test.go | 24 +- test/e2e/window_session_test.go | 194 ++-- test/e2e/window_sliding_test.go | 542 +++++----- test/e2e/window_tumbling_test.go | 558 +++++----- types/analytic.go | 78 +- types/config.go | 60 +- types/config_test.go | 60 +- types/match_recognize.go | 60 +- types/row_test.go | 40 +- types/timeslot_test.go | 62 +- utils/cast/cast.go | 16 +- utils/cast/cast_test.go | 12 +- utils/fieldpath/fieldpath_error_test.go | 76 +- utils/fieldpath/fieldpath_test.go | 24 +- utils/reflectutil/reflectutil_test.go | 58 +- utils/table/table_test.go | 38 +- utils/timex/time_test.go | 30 +- window/counting_window.go | 2 +- window/counting_window_ttl_test.go | 8 +- window/eventtime_tumbling_test.go | 28 +- window/performance_test.go | 56 +- window/session_window_regression_test.go | 10 +- window/sliding_window_test.go | 24 +- window/strategy_test.go | 72 +- window/tumbling_window_test.go | 24 +- window/window_test.go | 212 ++-- 198 files changed, 9685 insertions(+), 9684 deletions(-) diff --git a/.golangci.yml b/.golangci.yml index e32f63b..99f7c99 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -29,7 +29,7 @@ linters-settings: gosec: excludes: - - G404 # 随机数生成器可能不是密码学安全的 + - G404 # Random number generator may not be cryptographically secure gocritic: enabled-tags: @@ -50,30 +50,30 @@ linters-settings: issues: exclude-rules: - # 排除测试文件中的某些检查 + # Exclude selected checks in test files - path: _test\.go linters: - gosec - errcheck - gocyclo - # 排除CASE表达式测试中的复杂度检查 + # Exclude complexity checks in CASE expression tests - path: streamsql_case_test\.go linters: - gocyclo - funlen - # 排除generated文件 + # Exclude generated files - path: ".*\\.pb\\.go" linters: - all exclude: - # 排除一些常见的false positive + # Exclude common false positives - "Error return value of .((os\\.)?std(out|err)\\..*|.*Close|.*Flush|os\\.Remove(All)?|.*print.*|os\\.(Un)?Setenv). is not checked" - "should have a package comment" output: format: colored-line-number print-issued-lines: true - print-linter-name: true \ No newline at end of file + print-linter-name: true diff --git a/README.md b/README.md index 42dcf40..5fda517 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ [![codecov](https://codecov.io/gh/rulego/streamsql/graph/badge.svg?token=1CK1O5J1BI)](https://codecov.io/gh/rulego/streamsql) [![Mentioned in Awesome Go](https://awesome.re/mentioned-badge.svg)](https://github.com/avelino/awesome-go?tab=readme-ov-file#stream-processing) -English | [简体中文](README_ZH.md) +English | [Chinese Simplified](README_ZH.md) **StreamSQL** is an embeddable, SQL-based stream processing engine built for the IoT edge. It sits between a **time-series database** and **Apache Flink**: Flink-grade real-time computation with TSDB-grade lightweight deployment — run real-time filtering, windowed aggregation, CDC-style change detection, and complex event pattern matching, in-process, inside a 128MB gateway. diff --git a/aggregator/builtin.go b/aggregator/builtin.go index 81c3f10..f6127ec 100644 --- a/aggregator/builtin.go +++ b/aggregator/builtin.go @@ -44,9 +44,9 @@ type AggregatorFunction = functions.LegacyAggregatorFunction // ContextAggregator aggregator interface supporting context mechanism, re-exports functions.ContextAggregator type ContextAggregator = functions.ContextAggregator -// Register 将自定义聚合器注册到全局 registry(re-export functions.RegisterLegacyAggregator)。 -// 仅作 legacy 兜底:自定义聚合请实现 functions.AggregatorFunction 接口并用 functions.Register 注册, -// 适配器会自动接通,无需调用本函数。 +// Register: Registers custom aggregators to the global registry (re-export functions.RegisterLegacyAggregator). +// Only as a legacy backup: For custom aggregation, please implement the functions.AggregatorFunction interface and register with functions.Register, +// The adapter will automatically turn on without calling this function. func Register(name string, constructor func() AggregatorFunction) { functions.RegisterLegacyAggregator(name, constructor) } diff --git a/aggregator/builtin_test.go b/aggregator/builtin_test.go index c656515..1907124 100644 --- a/aggregator/builtin_test.go +++ b/aggregator/builtin_test.go @@ -7,67 +7,67 @@ import ( "github.com/stretchr/testify/require" ) -// TestPostAggregationPlaceholder 测试后聚合占位符的完整功能 +// TestPostAggregationPlaceholder Tests the full functionality of aggregated placeholders func TestPostAggregationPlaceholder(t *testing.T) { t.Run("测试PostAggregationPlaceholder基本功能", func(t *testing.T) { - // 创建PostAggregationPlaceholder实例 + // Create a PostAggregationPlaceholder instance placeholder := &PostAggregationPlaceholder{} require.NotNil(t, placeholder) - // 测试New方法 + // Test the new method newPlaceholder := placeholder.New() require.NotNil(t, newPlaceholder) assert.IsType(t, &PostAggregationPlaceholder{}, newPlaceholder) - // 测试Add方法(应该不做任何操作) + // Test the Add method (no action should be done) placeholder.Add(10) placeholder.Add("test") placeholder.Add(nil) placeholder.Add([]int{1, 2, 3}) - // 测试Result方法(应该返回nil) + // Test the Result method (should return nil) result := placeholder.Result() assert.Nil(t, result) }) t.Run("测试通过CreateBuiltinAggregator创建PostAggregationPlaceholder", func(t *testing.T) { - // 使用CreateBuiltinAggregator创建post_aggregation类型的聚合器 + // Use CreateBuiltinAggregator to create aggregators of post_aggregation type aggregator := CreateBuiltinAggregator(PostAggregation) require.NotNil(t, aggregator) assert.IsType(t, &PostAggregationPlaceholder{}, aggregator) - // 测试创建的聚合器功能 + // Test the aggregator features you create newAgg := aggregator.New() require.NotNil(t, newAgg) assert.IsType(t, &PostAggregationPlaceholder{}, newAgg) - // 测试添加各种类型的值 + // Test adds various types of values newAgg.Add(100) newAgg.Add("string_value") newAgg.Add(map[string]any{"key": "value"}) - // 验证结果始终为nil + // The verification result is always nil result := newAgg.Result() assert.Nil(t, result) }) t.Run("测试PostAggregationPlaceholder的多实例独立性", func(t *testing.T) { - // 创建多个实例 + // Create multiple instances placeholder1 := &PostAggregationPlaceholder{} placeholder2 := placeholder1.New() placeholder3 := placeholder1.New() - // 验证实例类型正确 + // Verify that the instance type is correct assert.IsType(t, &PostAggregationPlaceholder{}, placeholder1) assert.IsType(t, &PostAggregationPlaceholder{}, placeholder2) assert.IsType(t, &PostAggregationPlaceholder{}, placeholder3) - // 每个实例都应该返回nil + // Each instance should return nil assert.Nil(t, placeholder1.Result()) assert.Nil(t, placeholder2.Result()) assert.Nil(t, placeholder3.Result()) - // 验证Add操作不会影响结果(因为是占位符) + // Verifying that the Add operation does not affect the result (since it is a placeholder) placeholder1.Add("test1") placeholder2.Add("test2") placeholder3.Add("test3") @@ -77,18 +77,18 @@ func TestPostAggregationPlaceholder(t *testing.T) { }) t.Run("测试PostAggregationPlaceholder在聚合场景中的使用", func(t *testing.T) { - // 创建包含PostAggregationPlaceholder的聚合字段 + // Create an aggregate field containing PostAggregationPlaceholder groupFields := []string{"category"} aggFields := []AggregationField{ {InputField: "value", AggregateType: Sum, OutputAlias: "sum_value"}, {InputField: "placeholder_field", AggregateType: PostAggregation, OutputAlias: "post_agg_field"}, } - // 创建分组聚合器 + // Create a packet aggregator agg := NewGroupAggregator(groupFields, aggFields) require.NotNil(t, agg) - // 添加测试数据 + // Add test data testData := []map[string]any{ {"category": "A", "value": 10, "placeholder_field": "should_be_ignored"}, {"category": "A", "value": 20, "placeholder_field": "also_ignored"}, @@ -100,23 +100,23 @@ func TestPostAggregationPlaceholder(t *testing.T) { assert.NoError(t, err) } - // 获取结果 + // Get results results, err := agg.GetResults() assert.NoError(t, err) assert.Len(t, results, 2) - // 验证PostAggregationPlaceholder字段的结果为nil + // The result of verifying the PostAggregationPlaceholder field is nil for _, result := range results { assert.Contains(t, result, "post_agg_field") assert.Nil(t, result["post_agg_field"]) - // 验证正常聚合字段工作正常 + // Verify that the aggregated fields are working properly assert.Contains(t, result, "sum_value") assert.NotNil(t, result["sum_value"]) } }) } -// TestCreateBuiltinAggregatorPostAggregation 测试CreateBuiltinAggregator对post_aggregation类型的处理 +// TestCreateBuiltinAggregatorPostAggregation Test CreateBuiltinAggregator's handling of post_aggregation types func TestCreateBuiltinAggregatorPostAggregation(t *testing.T) { t.Run("测试post_aggregation类型聚合器创建", func(t *testing.T) { aggregator := CreateBuiltinAggregator("post_aggregation") @@ -125,22 +125,22 @@ func TestCreateBuiltinAggregatorPostAggregation(t *testing.T) { }) t.Run("测试PostAggregation常量", func(t *testing.T) { - // 验证PostAggregation常量值 + // Verify the PostAggregation constant value assert.Equal(t, AggregateType("post_aggregation"), PostAggregation) - // 使用常量创建聚合器 + // Create aggregators using constants aggregator := CreateBuiltinAggregator(PostAggregation) require.NotNil(t, aggregator) assert.IsType(t, &PostAggregationPlaceholder{}, aggregator) }) t.Run("测试与其他聚合类型的区别", func(t *testing.T) { - // 创建不同类型的聚合器 + // Create different types of aggregators sumAgg := CreateBuiltinAggregator(Sum) countAgg := CreateBuiltinAggregator(Count) postAgg := CreateBuiltinAggregator(PostAggregation) - // 验证类型不同 + // Different types of verification assert.NotEqual(t, sumAgg, postAgg) assert.NotEqual(t, countAgg, postAgg) assert.IsType(t, &PostAggregationPlaceholder{}, postAgg) diff --git a/aggregator/group_aggregator.go b/aggregator/group_aggregator.go index 7f392b1..35ea8e2 100644 --- a/aggregator/group_aggregator.go +++ b/aggregator/group_aggregator.go @@ -17,8 +17,8 @@ import ( // realistic field values. const nullGroupKeyMarker = "\x00NULL" -// groupKeySep 分隔分组键各字段。\x1f(单元分隔符)在真实数据中极少出现,避免字段值含 -// 分隔符导致的键碰撞(曾用 "|":含 "|" 的值会被还原阶段截断、多字段还会错位)。 +// groupKeySep separates the fields of the grouping key. \x1f (cell separator) rarely appears in real data to avoid field values containing +// Key collisions caused by delimiters (previously used with "|": Values containing "|" will be truncated during the restore phase, and multiple fields will be misaligned). const groupKeySep = "\x1f" // Aggregator aggregator interface @@ -43,7 +43,7 @@ type GroupAggregator struct { groupFields []string aggregators map[string]AggregatorFunction groups map[string]map[string]AggregatorFunction - groupKeyVals map[string][]any // 每个 group key 对应的原始类型分组字段值,供 GetResults 还原(避免序列化丢类型) + groupKeyVals map[string][]any // Each group key corresponds to the original type grouping field value, which GetResults restores (avoiding serialized type loss) mu sync.RWMutex context map[string]any // Expression evaluators @@ -152,9 +152,9 @@ func (ga *GroupAggregator) isNumericAggregator(aggType AggregateType) bool { return false } -// shouldAllowNullValues 判断聚合函数是否应该允许NULL值 +// shouldAllowNullValues determines whether the aggregator should allow NULL values func (ga *GroupAggregator) shouldAllowNullValues(aggType AggregateType) bool { - // FIRST_VALUE和LAST_VALUE函数应该允许NULL值,因为它们需要记录第一个/最后一个值,即使是NULL + // FIRST_VALUE and LAST_VALUE functions should allow NULL values because they need to record the first/last value, even if it is NULL return aggType == FirstValue || aggType == LastValue } @@ -162,7 +162,7 @@ func (ga *GroupAggregator) Add(data any) error { ga.mu.Lock() defer ga.mu.Unlock() - // 检查数据是否为nil + // Check if the data is nil if data == nil { return fmt.Errorf("data cannot be nil") } @@ -178,7 +178,7 @@ func (ga *GroupAggregator) Add(data any) error { if v.Kind() == reflect.Ptr { v = v.Elem() } - // 检查是否为支持的数据类型 + // Check if the data type is supported if v.Kind() != reflect.Struct && v.Kind() != reflect.Map { return fmt.Errorf("unsupported data type: %T, expected struct or map", data) } @@ -327,7 +327,7 @@ func (ga *GroupAggregator) Add(data any) error { groupAgg.Add(numVal) } } else { - // 非数值跳过该字段,不中断整行 Add。 + // Non-numeric values skip the field without interrupting the entire line of Add. continue } } else { @@ -346,7 +346,7 @@ func (ga *GroupAggregator) GetResults() ([]map[string]any, error) { ga.mu.RLock() defer ga.mu.RUnlock() - // 如果既没有分组字段又没有聚合字段,但有数据被添加过,返回一个空的结果行 + // If there are no grouping fields or aggregate fields, but data has been added, return an empty result row if len(ga.aggregationFields) == 0 && len(ga.groupFields) == 0 { if len(ga.groups) > 0 { return []map[string]any{{}}, nil @@ -360,7 +360,7 @@ func (ga *GroupAggregator) GetResults() ([]map[string]any, error) { keyVals := ga.groupKeyVals[key] for i, field := range ga.groupFields { if i < len(keyVals) { - group[field] = keyVals[i] // NULL 组此处即 nil + group[field] = keyVals[i] // The NULL group here is nil } } for field, agg := range aggregators { diff --git a/aggregator/group_aggregator_test.go b/aggregator/group_aggregator_test.go index 052f802..300b0e8 100644 --- a/aggregator/group_aggregator_test.go +++ b/aggregator/group_aggregator_test.go @@ -17,7 +17,7 @@ type testData struct { humidity float64 } -// TestGetResultsErrorCases 测试GetResults函数的错误情况 +// TestGetResultsErrorCases Tests for errors in the GetResults function func TestGetResultsErrorCases(t *testing.T) { groupFields := []string{"category"} aggFields := []AggregationField{ @@ -25,7 +25,7 @@ func TestGetResultsErrorCases(t *testing.T) { } agg := NewEnhancedGroupAggregator(groupFields, aggFields) - // 添加一个无效的后聚合表达式 + // Add an invalid post-aggregate expression requiredFields := []AggregationFieldInfo{ {FuncName: "invalid", InputField: "value", AggType: Sum}, } @@ -34,7 +34,7 @@ func TestGetResultsErrorCases(t *testing.T) { t.Skip("Expected error when adding invalid expression, but got none") } - // 测试获取结果时的错误处理 + // Error handling when testing results results, err := agg.GetResults() if err != nil { t.Errorf("Unexpected error: %v", err) @@ -44,7 +44,7 @@ func TestGetResultsErrorCases(t *testing.T) { } } -// TestParseFunctionCallEdgeCases 测试parseFunctionCall函数的边界情况 +// TestParseFunctionCallEdgeCases tests the boundary status of the parseFunctionCall function func TestParseFunctionCallEdgeCases(t *testing.T) { groupFields := []string{"category"} aggFields := []AggregationField{ @@ -97,7 +97,7 @@ func TestParseFunctionCallEdgeCases(t *testing.T) { } } -// TestHasMultipleTopLevelArgsEdgeCases 测试hasMultipleTopLevelArgs函数的边界情况 +// TestHasMultipleTopLevelArgsEdgeCases tests the boundary status of the hasMultipleTopLevelArgs function func TestHasMultipleTopLevelArgsEdgeCases(t *testing.T) { tests := []struct { name string @@ -151,7 +151,7 @@ func TestHasMultipleTopLevelArgsEdgeCases(t *testing.T) { } } -// TestBuiltinAggregatorEdgeCases 测试内置聚合器的边界情况 +// TestBuiltinAggregatorEdgeCases tests the boundaries of the built-in aggregator func TestBuiltinAggregatorEdgeCases(t *testing.T) { tests := []struct { name string @@ -236,7 +236,7 @@ func TestGroupAggregator_MultiFieldSum(t *testing.T) { assert.ElementsMatch(t, expected, results) } -// TestGroupAggregator_Put 测试Put方法 +// TestGroupAggregator_Put Test the Put method func TestGroupAggregator_Put(t *testing.T) { agg := NewGroupAggregator( []string{"Device"}, @@ -249,18 +249,18 @@ func TestGroupAggregator_Put(t *testing.T) { }, ) - // 测试Put方法 + // Test the put method err := agg.Put("test_key", "test_value") assert.NoError(t, err) - // 测试多次Put + // Test multiple puts err = agg.Put("key1", 123) assert.NoError(t, err) err = agg.Put("key2", 456.78) assert.NoError(t, err) } -// TestGroupAggregator_RegisterExpression 测试表达式注册 +// TestGroupAggregator_RegisterExpression Test expression registration func TestGroupAggregator_RegisterExpression(t *testing.T) { agg := NewGroupAggregator( []string{"Device"}, @@ -273,12 +273,12 @@ func TestGroupAggregator_RegisterExpression(t *testing.T) { }, ) - // 注册表达式 + // Register the expression evaluator := func(data any) (any, error) { if dataMap, ok := data.(map[string]any); ok { if temp, exists := dataMap["temperature"]; exists { if tempFloat, ok := temp.(float64); ok { - return tempFloat*1.8 + 32, nil // 摄氏度转华氏度 + return tempFloat*1.8 + 32, nil // Celsius degrees are converted to Fahrenheit degrees } } } @@ -287,14 +287,14 @@ func TestGroupAggregator_RegisterExpression(t *testing.T) { agg.RegisterExpression("fahrenheit", "temperature * 1.8 + 32", []string{"temperature"}, evaluator) - // 验证表达式已注册 + // The verification expression is registered assert.NotNil(t, agg.expressions["fahrenheit"]) assert.Equal(t, "fahrenheit", agg.expressions["fahrenheit"].Field) assert.Equal(t, "temperature * 1.8 + 32", agg.expressions["fahrenheit"].Expression) assert.Equal(t, []string{"temperature"}, agg.expressions["fahrenheit"].Fields) } -// TestGroupAggregator_Reset 测试Reset方法 +// TestGroupAggregator_Reset Test the reset method func TestGroupAggregator_Reset(t *testing.T) { agg := NewGroupAggregator( []string{"Device"}, @@ -307,7 +307,7 @@ func TestGroupAggregator_Reset(t *testing.T) { }, ) - // 添加一些数据 + // Add some data testData := []map[string]any{ {"Device": "test", "temperature": 25.5}, {"Device": "test", "temperature": 26.8}, @@ -317,20 +317,20 @@ func TestGroupAggregator_Reset(t *testing.T) { agg.Add(d) } - // 验证有数据 + // Verification has data results, err := agg.GetResults() assert.NoError(t, err) assert.Len(t, results, 1) - // 重置 + // Reset agg.Reset() - // 验证数据已清空 + // Verification data has been cleared results, _ = agg.GetResults() assert.Len(t, results, 0) } -// TestGroupAggregator_ErrorHandling 测试错误处理 +// TestGroupAggregator_ErrorHandling Handling of test errors func TestGroupAggregator_ErrorHandling(t *testing.T) { agg := NewGroupAggregator( []string{"Device"}, @@ -343,20 +343,20 @@ func TestGroupAggregator_ErrorHandling(t *testing.T) { }, ) - // 测试添加无效数据 + // Testing adds invalid data err := agg.Add(nil) assert.Error(t, err) - // 测试添加非map类型数据 + // Test adding non-map type data err = agg.Add("invalid data") assert.Error(t, err) - // 缺少分组字段:归入 NULL 分组,不再报错 + // Missing grouping fields: Incorporate NULL grouping, no longer causing errors err = agg.Add(map[string]any{"temperature": 25.5}) assert.NoError(t, err) } -// 数值 GROUP BY 键必须保留原始类型输出(曾恒为 string);int(1) 与 float64(1) 应同组。 +// The GROUP BY key must retain the original type output (Zeng Heng is string); int(1) and float64(1) should be in the same group. func TestGroupAggregator_GroupKeyPreservesType(t *testing.T) { for _, tc := range []struct { name string @@ -378,7 +378,7 @@ func TestGroupAggregator_GroupKeyPreservesType(t *testing.T) { assert.IsType(t, tc.val, got, "%s: region_id must keep original type, not become string", tc.name) } - // int(1) 与 float64(1) 同值应分到同一组(SQL 数值相等),不得拆成两组。 + // int(1) and float64(1) with the same value should be grouped together (SQL values are equal), and must not be split into two groups. agg := NewGroupAggregator([]string{"region_id"}, []AggregationField{ {InputField: "*", AggregateType: Count, OutputAlias: "cnt"}, }) @@ -390,7 +390,7 @@ func TestGroupAggregator_GroupKeyPreservesType(t *testing.T) { assert.Equal(t, float64(2), res[0]["cnt"]) } -// 字段值含分隔符必须完整保留(曾用 "|" 还原时被 Split 截断为 "a")。 +// Field values with separators must be fully preserved (previously truncated as "a" by Split when restoring with "|"). func TestGroupAggregator_GroupKeyWithSeparator(t *testing.T) { agg := NewGroupAggregator([]string{"tag"}, []AggregationField{ {InputField: "*", AggregateType: Count, OutputAlias: "cnt"}, @@ -411,8 +411,8 @@ func TestGroupAggregator_GroupKeyWithSeparator(t *testing.T) { assert.Equal(t, 1, seen["x"]) } -// 数值聚合字段 cast 失败应只跳过该字段该行,不得 return 中断整行 Add(曾导致同行 -// 其后字段漏算)。切片顺序 [b,c] 下 b 先于 c:行 {b:"x",c:2} 的 c 必须仍被 SUM(c) 计入。 +// If the numeric aggregate field cast fails, only skip that field and the corresponding line; do not return interrupt the entire line Add (which has caused competitors). +// The following fields are omitted). In the slice order [b,c], b precedes the c in row {b:"x", c:2} must still be counted by SUM(c). func TestGroupAggregator_NumericCastFailureSkipsFieldOnly(t *testing.T) { agg := NewGroupAggregator(nil, []AggregationField{ {InputField: "b", AggregateType: Sum, OutputAlias: "sum_b"}, @@ -420,20 +420,20 @@ func TestGroupAggregator_NumericCastFailureSkipsFieldOnly(t *testing.T) { }) rows := []map[string]any{ {"b": 10, "c": 1}, - {"b": "x", "c": 2}, // b 非数值:只跳过 sum_b,sum_c 仍计 2 + {"b": "x", "c": 2}, // b Non-numeric value: skipping only sum_b, sum_c still counts as 2 {"b": 20, "c": 3}, } for _, r := range rows { - require.NoError(t, agg.Add(r)) // 修复前 Add(row2) 返回 cast error + require.NoError(t, agg.Add(r)) // Before repairing, Add(row2) returned a cast error } res, err := agg.GetResults() require.NoError(t, err) require.Len(t, res, 1) - assert.Equal(t, float64(30), res[0]["sum_b"]) // 10+20,"x" 跳过 - assert.Equal(t, float64(6), res[0]["sum_c"]) // 1+2+3,修复前=4(return 中断漏算 c) + assert.Equal(t, float64(30), res[0]["sum_b"]) // 10+20, skip with "x"." + assert.Equal(t, float64(6), res[0]["sum_c"]) // 1+2+3, before repair = 4 (return interrupt omitted c) } -// TestGroupAggregator_DifferentAggregateTypes 测试不同聚合类型 +// TestGroupAggregator_DifferentAggregateTypes Test different types of aggregation func TestGroupAggregator_DifferentAggregateTypes(t *testing.T) { agg := NewGroupAggregator( []string{"category"}, @@ -478,7 +478,7 @@ func TestGroupAggregator_DifferentAggregateTypes(t *testing.T) { assert.NoError(t, err) assert.Len(t, results, 2) - // 验证结果 + // Verify the results for _, result := range results { category := result["category"] if category == "A" { @@ -495,7 +495,7 @@ func TestGroupAggregator_DifferentAggregateTypes(t *testing.T) { } } -// TestGroupAggregator_MultipleGroupFields 测试多个分组字段 +// TestGroupAggregator_MultipleGroupFields Test multiple grouped fields func TestGroupAggregator_MultipleGroupFields(t *testing.T) { agg := NewGroupAggregator( []string{"region", "category"}, @@ -525,7 +525,7 @@ func TestGroupAggregator_MultipleGroupFields(t *testing.T) { assert.NoError(t, err) assert.Len(t, results, 4) - // 验证每个组合的结果 + // Verify the results of each combination expected := map[string]float64{ "North-A": 250.0, "North-B": 200.0, @@ -541,7 +541,7 @@ func TestGroupAggregator_MultipleGroupFields(t *testing.T) { } } -// TestGroupAggregator_EmptyData 测试空数据处理 +// TestGroupAggregator_EmptyData Testspace data processing func TestGroupAggregator_EmptyData(t *testing.T) { agg := NewGroupAggregator( []string{"Device"}, @@ -554,13 +554,13 @@ func TestGroupAggregator_EmptyData(t *testing.T) { }, ) - // 不添加任何数据,直接获取结果 + // No data is added, just get the results directly results, err := agg.GetResults() assert.NoError(t, err) assert.Len(t, results, 0) } -// TestGroupAggregator_NilValues 测试空值处理 +// TestGroupAggregator_NilValues Test null value handling func TestGroupAggregator_NilValues(t *testing.T) { agg := NewGroupAggregator( []string{"Device"}, @@ -575,7 +575,7 @@ func TestGroupAggregator_NilValues(t *testing.T) { testData := []map[string]any{ {"Device": "test", "temperature": 25.5}, - {"Device": "test", "temperature": nil}, // 空值 + {"Device": "test", "temperature": nil}, // Null value {"Device": "test", "temperature": 30.0}, } @@ -588,12 +588,12 @@ func TestGroupAggregator_NilValues(t *testing.T) { assert.NoError(t, err) assert.Len(t, results, 1) - // 空值应该被忽略,只计算非空值 + // Null values should be ignored, and only non-null values are calculated expected := 55.5 // 25.5 + 30.0 assert.Equal(t, expected, results[0]["temperature_sum"]) } -// TestGroupAggregator_ConcurrentAccess 测试并发访问 +// TestGroupAggregator_ConcurrentAccess Test for concurrent access func TestGroupAggregator_ConcurrentAccess(t *testing.T) { agg := NewGroupAggregator( []string{"Device"}, @@ -606,7 +606,7 @@ func TestGroupAggregator_ConcurrentAccess(t *testing.T) { }, ) - // 并发添加数据 + // Concurrent data addition go func() { for i := 0; i < 10; i++ { agg.Add(map[string]any{"Device": "A", "temperature": float64(i)}) @@ -619,7 +619,7 @@ func TestGroupAggregator_ConcurrentAccess(t *testing.T) { } }() - // 并发注册表达式 + // Concurrent registration expressions go func() { evaluator := func(data any) (any, error) { return 1.0, nil @@ -627,23 +627,23 @@ func TestGroupAggregator_ConcurrentAccess(t *testing.T) { agg.RegisterExpression("test_expr", "1", []string{}, evaluator) }() - // 并发Put操作 + // Concurrent Put operations go func() { for i := 0; i < 5; i++ { agg.Put("key"+string(rune(i)), i) } }() - // 等待一段时间确保所有goroutine完成 - // 注意:这不是最佳的同步方式,但对于测试来说足够了 - // 在实际应用中应该使用sync.WaitGroup + // Wait a while to ensure all goroutines are completed + // Note: This is not the best synchronization method, but it is sufficient for testing + // In practical applications, sync.WaitGroup for i := 0; i < 100; i++ { - // 尝试获取结果,测试并发读取 + // Attempt to obtain results and test concurrent reads _, _ = agg.GetResults() } } -// TestCreateBuiltinAggregator 测试内置聚合器创建 +// TestCreateBuiltinAggregator tests the creation of a built-in aggregator func TestCreateBuiltinAggregator(t *testing.T) { tests := []struct { name string @@ -662,28 +662,28 @@ func TestCreateBuiltinAggregator(t *testing.T) { aggregator := CreateBuiltinAggregator(tt.aggType) assert.NotNil(t, aggregator) - // 测试New方法 + // Test the new method newAgg := aggregator.New() assert.NotNil(t, newAgg) }) } } -// TestExpressionAggregatorWrapper 测试表达式聚合器包装器 +// TestExpressionAggregatorWrapper Tests the expression aggregator wrapper func TestExpressionAggregatorWrapper(t *testing.T) { wrapper := CreateBuiltinAggregator(Expression) require.NotNil(t, wrapper) - // 测试类型断言 + // Test type assertion exprWrapper, ok := wrapper.(*ExpressionAggregatorWrapper) assert.True(t, ok) assert.NotNil(t, exprWrapper.function) - // 测试New方法 + // Test the new method newWrapper := wrapper.New() assert.NotNil(t, newWrapper) - // 测试Add和Result方法 + // Test the Add and Result methods wrapper.Add(10.0) wrapper.Add(20.0) result := wrapper.Result() @@ -799,9 +799,9 @@ func TestGroupAggregator_NoAlias(t *testing.T) { assert.ElementsMatch(t, expected, results) } -// TestGroupAggregatorAdvancedFeatures 测试聚合器高级功能 +// TestGroupAggregatorAdvancedFeatures Advanced features of the test aggregator func TestGroupAggregatorAdvancedFeatures(t *testing.T) { - // 测试复杂聚合表达式 + // Test complex aggregated expressions t.Run("Complex Aggregation Expressions", func(t *testing.T) { agg := NewGroupAggregator( []string{"Device"}, @@ -839,7 +839,7 @@ func TestGroupAggregatorAdvancedFeatures(t *testing.T) { assert.NoError(t, err) assert.Len(t, results, 2) - // 验证结果 + // Verify the results for _, result := range results { device := result["Device"].(string) if device == "sensor1" { @@ -854,7 +854,7 @@ func TestGroupAggregatorAdvancedFeatures(t *testing.T) { } }) - // 测试统计聚合函数 + // Test statistical aggregation functions t.Run("Statistical Aggregation Functions", func(t *testing.T) { tests := []struct { name string @@ -895,7 +895,7 @@ func TestGroupAggregatorAdvancedFeatures(t *testing.T) { }) } -// TestGroupAggregatorDataTypes 测试不同数据类型的聚合 +// TestGroupAggregatorDataTypes tests aggregations of different data types func TestGroupAggregatorDataTypes(t *testing.T) { tests := []struct { name string @@ -913,7 +913,7 @@ func TestGroupAggregatorDataTypes(t *testing.T) { {"group": "B", "value": "test"}, }, expectedKey: "count", - expectedVal: 2.0, // Count聚合器计算所有非空值 + expectedVal: 2.0, // The Count aggregator calculates all non-null values }, { name: "Boolean Count", @@ -925,7 +925,7 @@ func TestGroupAggregatorDataTypes(t *testing.T) { {"group": "B", "value": false}, }, expectedKey: "count", - expectedVal: 3.0, // Count聚合器计算所有非空值 + expectedVal: 3.0, // The Count aggregator calculates all non-null values }, { name: "Mixed Types Count", @@ -937,7 +937,7 @@ func TestGroupAggregatorDataTypes(t *testing.T) { {"group": "B", "value": 456}, }, expectedKey: "count", - expectedVal: 3.0, // Count聚合器计算所有非空值 + expectedVal: 3.0, // The Count aggregator calculates all non-null values }, } @@ -961,7 +961,7 @@ func TestGroupAggregatorDataTypes(t *testing.T) { results, err := agg.GetResults() assert.NoError(t, err) - // 找到A组的结果 + // Find the results of Group A var groupAResult map[string]any for _, result := range results { if result["group"] == "A" { @@ -976,9 +976,9 @@ func TestGroupAggregatorDataTypes(t *testing.T) { } } -// TestGroupAggregatorEdgeCases 测试聚合器边界情况 +// TestGroupAggregatorEdgeCases tests aggregator boundaries func TestGroupAggregatorEdgeCases(t *testing.T) { - // 测试空数据 + // Test empty data t.Run("Empty Data", func(t *testing.T) { agg := NewGroupAggregator( []string{"Device"}, @@ -996,7 +996,7 @@ func TestGroupAggregatorEdgeCases(t *testing.T) { assert.Empty(t, results) }) - // 测试空分组字段 + // Test empty group fields t.Run("Empty Group Fields", func(t *testing.T) { agg := NewGroupAggregator( []string{}, @@ -1024,7 +1024,7 @@ func TestGroupAggregatorEdgeCases(t *testing.T) { assert.InDelta(t, 52.3, results[0]["temperature_sum"], 0.01) }) - // 测试空聚合字段 + // Test the empty aggregate field t.Run("Empty Aggregation Fields", func(t *testing.T) { agg := NewGroupAggregator( []string{"Device"}, @@ -1043,14 +1043,14 @@ func TestGroupAggregatorEdgeCases(t *testing.T) { results, err := agg.GetResults() assert.NoError(t, err) assert.Len(t, results, 2) - // 只应该包含分组字段,没有聚合字段 + // Only grouped fields should be included, no aggregated fields for _, result := range results { assert.Contains(t, result, "Device") assert.Len(t, result, 1) } }) - // 测试缺失字段 + // Test for missing fields t.Run("Missing Fields", func(t *testing.T) { agg := NewGroupAggregator( []string{"Device"}, @@ -1065,7 +1065,7 @@ func TestGroupAggregatorEdgeCases(t *testing.T) { testData := []map[string]any{ {"Device": "sensor1", "temperature": 25.5}, - {"Device": "sensor2"}, // 缺少temperature字段 + {"Device": "sensor2"}, // Missing the temperature field {"Device": "sensor3", "temperature": 26.8}, } @@ -1077,13 +1077,13 @@ func TestGroupAggregatorEdgeCases(t *testing.T) { assert.NoError(t, err) assert.Len(t, results, 3) - // 验证结果 + // Verify the results for _, result := range results { device := result["Device"].(string) if device == "sensor1" { assert.Equal(t, 25.5, result["temperature_sum"]) } else if device == "sensor2" { - assert.Nil(t, result["temperature_sum"]) // 缺失字段应该为nil + assert.Nil(t, result["temperature_sum"]) // The missing field should be nil } else if device == "sensor3" { assert.Equal(t, 26.8, result["temperature_sum"]) } @@ -1091,9 +1091,9 @@ func TestGroupAggregatorEdgeCases(t *testing.T) { }) } -// TestGroupAggregatorPerformance 测试聚合器性能 +// TestGroupAggregatorPerformance Tests aggregator performance func TestGroupAggregatorPerformance(t *testing.T) { - // 测试大量数据处理性能 + // Testing large amounts of data processing performance t.Run("Large Dataset Performance", func(t *testing.T) { agg := NewGroupAggregator( []string{"category"}, @@ -1121,7 +1121,7 @@ func TestGroupAggregatorPerformance(t *testing.T) { }, ) - // 生成大量测试数据 + // Generates a large amount of test data const numRecords = 10000 const numCategories = 100 @@ -1139,7 +1139,7 @@ func TestGroupAggregatorPerformance(t *testing.T) { assert.NoError(t, err) assert.Len(t, results, numCategories) - // 验证结果 + // Verify the results for _, result := range results { assert.Contains(t, result, "sum") assert.Contains(t, result, "avg") @@ -1148,7 +1148,7 @@ func TestGroupAggregatorPerformance(t *testing.T) { } }) - // 测试并发性能 + // Testing concurrency performance t.Run("Concurrent Performance", func(t *testing.T) { agg := NewGroupAggregator( []string{"category"}, @@ -1167,7 +1167,7 @@ func TestGroupAggregatorPerformance(t *testing.T) { var wg sync.WaitGroup wg.Add(numGoroutines) - // 启动多个goroutine并发添加数据 + // Start multiple goroutines to add data concurrently for i := 0; i < numGoroutines; i++ { go func(goroutineID int) { defer wg.Done() @@ -1191,9 +1191,9 @@ func TestGroupAggregatorPerformance(t *testing.T) { }) } -// TestGroupAggregatorMemoryUsage 测试聚合器内存使用 +// TestGroupAggregatorMemoryUsage tests aggregator memory usage func TestGroupAggregatorMemoryUsage(t *testing.T) { - // 测试大量分组的内存使用 + // Testing memory usage across large packets t.Run("Many Groups Memory Usage", func(t *testing.T) { agg := NewGroupAggregator( []string{"group"}, @@ -1206,7 +1206,7 @@ func TestGroupAggregatorMemoryUsage(t *testing.T) { }, ) - // 创建大量不同的分组 + // Create a large number of different groups const numGroups = 10000 for i := 0; i < numGroups; i++ { data := map[string]any{ @@ -1220,7 +1220,7 @@ func TestGroupAggregatorMemoryUsage(t *testing.T) { assert.NoError(t, err) assert.Len(t, results, numGroups) - // 验证每个分组都有正确的结果 + // Verify that each group has the correct results for i := 0; i < numGroups; i++ { expectedGroup := fmt.Sprintf("group_%d", i) found := false @@ -1236,7 +1236,7 @@ func TestGroupAggregatorMemoryUsage(t *testing.T) { }) } -// TestGroupAggregatorResetAndReuse 测试聚合器重置和重用 +// TestGroupAggregatorResetAndReuse: Test aggregator reset and reuse func TestGroupAggregatorResetAndReuse(t *testing.T) { agg := NewGroupAggregator( []string{"category"}, @@ -1249,7 +1249,7 @@ func TestGroupAggregatorResetAndReuse(t *testing.T) { }, ) - // 第一轮数据 + // First round data testData1 := []map[string]any{ {"category": "A", "value": 10.0}, {"category": "B", "value": 20.0}, @@ -1263,10 +1263,10 @@ func TestGroupAggregatorResetAndReuse(t *testing.T) { assert.NoError(t, err) assert.Len(t, results1, 2) - // 重置聚合器 + // Reset the aggregator agg.Reset() - // 第二轮数据 + // Second round data testData2 := []map[string]any{ {"category": "A", "value": 15.0}, {"category": "C", "value": 25.0}, @@ -1280,7 +1280,7 @@ func TestGroupAggregatorResetAndReuse(t *testing.T) { assert.NoError(t, err) assert.Len(t, results2, 2) - // 验证第二轮结果 + // Verify the results of the second round for _, result := range results2 { category := result["category"].(string) if category == "A" { @@ -1291,9 +1291,9 @@ func TestGroupAggregatorResetAndReuse(t *testing.T) { } } -// TestGroupAggregatorBasic 测试基本聚合器功能 +// TestGroupAggregatorBasic tests basic aggregator functionality func TestGroupAggregatorBasic(t *testing.T) { - // 创建聚合字段配置 + // Create aggregated field configurations aggFields := []AggregationField{ { InputField: "value", @@ -1302,17 +1302,17 @@ func TestGroupAggregatorBasic(t *testing.T) { }, } - // 创建分组聚合器 + // Create a packet aggregator ga := NewGroupAggregator([]string{"group"}, aggFields) - // 测试数据 + // Test data data := []map[string]any{ {"group": "A", "value": 10}, {"group": "A", "value": 20}, {"group": "B", "value": 30}, } - // 添加数据 + // Add data for _, item := range data { err := ga.Add(item) if err != nil { @@ -1320,42 +1320,42 @@ func TestGroupAggregatorBasic(t *testing.T) { } } - // 获取结果 + // Get results results, err := ga.GetResults() if err != nil { t.Errorf("unexpected error: %v", err) } - // 验证结果 + // Verify the results if len(results) != 2 { t.Errorf("expected 2 groups, got %d", len(results)) } } -// TestGroupAggregatorErrorHandling 测试错误处理 +// TestGroupAggregatorErrorHandling Test error handling func TestGroupAggregatorErrorHandling(t *testing.T) { - // 测试空配置 + // Test the empty configuration ga := NewGroupAggregator([]string{}, []AggregationField{}) - // 添加数据应该不会出错 + // Adding data shouldn't go wrong err := ga.Add(map[string]any{"field": "value"}) if err != nil { t.Errorf("unexpected error: %v", err) } - // 获取结果 + // Get results results, err := ga.GetResults() if err != nil { t.Errorf("unexpected error: %v", err) } - // 空配置应该返回空结果 + // An empty configuration should return an empty result if len(results) != 1 { t.Errorf("expected 1 result, got %d", len(results)) } } -// TestGroupAggregatorConcurrency 测试并发安全 +// TestGroupAggregatorConcurrency tests concurrency security func TestGroupAggregatorConcurrency(t *testing.T) { aggFields := []AggregationField{ { @@ -1367,7 +1367,7 @@ func TestGroupAggregatorConcurrency(t *testing.T) { ga := NewGroupAggregator([]string{"group"}, aggFields) - // 并发添加数据 + // Concurrent data addition for i := 0; i < 100; i++ { go func(id int) { data := map[string]any{ @@ -1381,37 +1381,37 @@ func TestGroupAggregatorConcurrency(t *testing.T) { }(i) } - // 等待一段时间确保所有goroutine完成 + // Wait a while to ensure all goroutines are completed time.Sleep(100 * time.Millisecond) - // 获取结果 + // Get results results, err := ga.GetResults() if err != nil { t.Errorf("unexpected error: %v", err) } - // 验证结果存在 + // The verification results exist if len(results) == 0 { t.Error("expected results, got none") } } -// TestRegisterFunction 测试 Register 函数 +// TestRegisterFunction Register function func TestRegisterFunction(t *testing.T) { - // 创建自定义聚合器 + // Create custom aggregators customAggregator := func() AggregatorFunction { return &testCustomAggregator{} } - // 注册自定义聚合器 + // Register a custom aggregator Register("custom_test", customAggregator) - // 验证注册成功(通过创建聚合器来验证) + // Verify registration success (by creating aggregators to verify it) agg := CreateBuiltinAggregator("custom_test") assert.NotNil(t, agg) } -// testCustomAggregator 测试用的自定义聚合器 +// testCustomAggregator is a custom aggregator for testing type testCustomAggregator struct { sum float64 } @@ -1430,11 +1430,11 @@ func (t *testCustomAggregator) Result() any { return t.sum } -// TestIsNumericAggregator 测试 isNumericAggregator 方法的各种分支 +// TestIsNumericAggregator tests various branches of the isNumericAggregator method func TestIsNumericAggregator(t *testing.T) { ga := NewGroupAggregator([]string{"group"}, []AggregationField{}) - // 测试数值聚合器 + // Test numerical aggregator assert.True(t, ga.isNumericAggregator(Sum)) assert.True(t, ga.isNumericAggregator(Avg)) assert.True(t, ga.isNumericAggregator(Max)) @@ -1447,19 +1447,19 @@ func TestIsNumericAggregator(t *testing.T) { assert.True(t, ga.isNumericAggregator(VarS)) assert.True(t, ga.isNumericAggregator(StdDevS)) - // 测试非数值聚合器 + // Testing non-numerical aggregators assert.False(t, ga.isNumericAggregator(Collect)) assert.False(t, ga.isNumericAggregator(MergeAgg)) assert.False(t, ga.isNumericAggregator(Deduplicate)) assert.False(t, ga.isNumericAggregator(LastValue)) - // 测试分析函数 + // Test the analysis function assert.False(t, ga.isNumericAggregator(Lag)) assert.False(t, ga.isNumericAggregator(Latest)) assert.False(t, ga.isNumericAggregator(ChangedCol)) assert.False(t, ga.isNumericAggregator(HadChanged)) - // 测试未知聚合器(通过名称模式匹配) + // Testing unknown aggregators (by name pattern matching) assert.True(t, ga.isNumericAggregator("custom_sum")) assert.True(t, ga.isNumericAggregator("custom_avg")) assert.True(t, ga.isNumericAggregator("custom_min")) @@ -1468,33 +1468,33 @@ func TestIsNumericAggregator(t *testing.T) { assert.True(t, ga.isNumericAggregator("custom_std")) assert.True(t, ga.isNumericAggregator("custom_var")) - // 测试不匹配模式的未知聚合器 + // Testing an unknown aggregator with mismatched patterns assert.False(t, ga.isNumericAggregator("custom_collect")) assert.False(t, ga.isNumericAggregator("unknown_function")) } -// TestExpressionAggregator 测试表达式聚合器 +// TestExpressionAggregator tests the expression aggregator func TestExpressionAggregator(t *testing.T) { - // 创建表达式聚合器 + // Create an expression aggregator agg := CreateBuiltinAggregator(Expression) assert.NotNil(t, agg) - // 测试创建新实例 + // Test to create new instances newAgg := agg.New() assert.NotNil(t, newAgg) - // 测试添加值和获取结果 + // Test the added value and obtain the results newAgg.Add("test_value") result := newAgg.Result() assert.NotNil(t, result) } -// TestGroupAggregatorContextAggregator 测试 ContextAggregator 功能 +// TestGroupAggregatorContextAggregator tests the ContextAggregator feature func TestGroupAggregatorContextAggregator(t *testing.T) { - // 创建一个共享的values切片来跟踪所有添加的值 + // Create a shared values slice to track all added values sharedValues := &[]any{} - // 注册模拟聚合器 + // Register the analog aggregator Register("mock_context", func() AggregatorFunction { return &mockContextAggregator{ contextKey: "test_context_key", @@ -1506,34 +1506,34 @@ func TestGroupAggregatorContextAggregator(t *testing.T) { []string{"group"}, []AggregationField{ { - InputField: "missing_field", // 故意使用不存在的字段 + InputField: "missing_field", // Deliberately using fields that don't exist AggregateType: "mock_context", OutputAlias: "context_result", }, }, ) - // 设置上下文值 + // Set context values err := ga.Put("test_context_key", "context_value") assert.NoError(t, err) - // 添加数据(字段不存在,应该从上下文获取) + // Add data (fields do not exist and should be retrieved from context) err = ga.Add(map[string]any{ "group": "test_group", - // 故意不包含 missing_field + // Intentionally omitting missing_field }) assert.NoError(t, err) - // 获取结果 + // Get results results, err := ga.GetResults() assert.NoError(t, err) assert.Len(t, results, 1) - // 验证上下文值被使用 + // Verify that context values are used assert.Contains(t, *sharedValues, "context_value") } -// mockContextAggregator 模拟的 ContextAggregator +// mockContextAggregator Simulates ContextAggregator type mockContextAggregator struct { contextKey string values *[]any @@ -1542,7 +1542,7 @@ type mockContextAggregator struct { func (m *mockContextAggregator) New() AggregatorFunction { return &mockContextAggregator{ contextKey: m.contextKey, - values: m.values, // 共享同一个values切片 + values: m.values, // Share the same values slices } } @@ -1558,33 +1558,33 @@ func (m *mockContextAggregator) GetContextKey() string { return m.contextKey } -// TestGroupAggregatorNumericConversionError 非数值字段应被数值聚合跳过(不中断整行 -// Add、不报错),其余有效值照常聚合(A5:曾 return 错误中断整行 Add,致同行其后字段漏算)。 +// TestGroupAggregatorNumericConversionError Non-numeric fields should be skipped by numeric aggregation (without interrupting the entire line). +// Add, no error), and the remaining valid values are aggregated as usual (A5: Zeng returned errors interrupted the entire line of Add, causing the following fields to be omitted). func TestGroupAggregatorNumericConversionError(t *testing.T) { ga := NewGroupAggregator( []string{"group"}, []AggregationField{ { InputField: "value", - AggregateType: Sum, // Sum 需要数值类型 + AggregateType: Sum, // Sum requires a numeric type OutputAlias: "sum_value", }, }, ) - // 非数值行:跳过 value,不应报错 + // Non-numeric rows: skip the value and should not report errors require.NoError(t, ga.Add(map[string]any{"group": "g1", "value": "not_a_number"})) - // 有效行:照常聚合 + // Effective row: Polymerization as usual require.NoError(t, ga.Add(map[string]any{"group": "g1", "value": 10})) require.NoError(t, ga.Add(map[string]any{"group": "g1", "value": 20})) res, err := ga.GetResults() require.NoError(t, err) require.Len(t, res, 1) - assert.Equal(t, float64(30), res[0]["sum_value"]) // 仅 10+20,"not_a_number" 被跳过 + assert.Equal(t, float64(30), res[0]["sum_value"]) // Only 10+20, the "not_a_number" was skipped } -// TestGroupAggregatorWithExpressionEvaluator 测试表达式求值器 +// TestGroupAggregatorWithExpressionEvaluator: Test expression evaluator func TestGroupAggregatorWithExpressionEvaluator(t *testing.T) { ga := NewGroupAggregator( []string{"group"}, @@ -1597,7 +1597,7 @@ func TestGroupAggregatorWithExpressionEvaluator(t *testing.T) { }, ) - // 注册表达式求值器(摄氏度转华氏度) + // Register expression evaluator (Celsius to Fahrenheit) evaluator := func(data any) (any, error) { if dataMap, ok := data.(map[string]any); ok { if temp, exists := dataMap["temperature"]; exists { @@ -1611,7 +1611,7 @@ func TestGroupAggregatorWithExpressionEvaluator(t *testing.T) { ga.RegisterExpression("fahrenheit_sum", "temperature * 1.8 + 32", []string{"temperature"}, evaluator) - // 添加测试数据 + // Add test data testData := []map[string]any{ {"group": "sensor1", "temperature": 0.0}, // 32°F {"group": "sensor1", "temperature": 100.0}, // 212°F @@ -1622,17 +1622,17 @@ func TestGroupAggregatorWithExpressionEvaluator(t *testing.T) { assert.NoError(t, err) } - // 获取结果 + // Get results results, err := ga.GetResults() assert.NoError(t, err) assert.Len(t, results, 1) - // 验证表达式求值结果 (32 + 212 = 244) + // Verify the evaluation result of the expression (32 + 212 = 244) assert.Equal(t, "sensor1", results[0]["group"]) assert.Equal(t, 244.0, results[0]["fahrenheit_sum"]) } -// TestGroupAggregatorExpressionEvaluatorError 测试表达式求值器错误处理 +// TestGroupAggregatorExpressionEvaluatorError Error handling of test expression evaluator func TestGroupAggregatorExpressionEvaluatorError(t *testing.T) { ga := NewGroupAggregator( []string{"group"}, @@ -1645,50 +1645,50 @@ func TestGroupAggregatorExpressionEvaluatorError(t *testing.T) { }, ) - // 注册会出错的表达式求值器 + // Register an error expression evaluator errorEvaluator := func(data any) (any, error) { return nil, errors.New("expression evaluation failed") } ga.RegisterExpression("processed_value", "error_expression", []string{"value"}, errorEvaluator) - // 添加测试数据 + // Add test data err := ga.Add(map[string]any{ "group": "test_group", "value": 10.0, }) - // 应该没有错误,因为表达式错误会被忽略 + // There should be no errors, because expression errors are ignored assert.NoError(t, err) - // 获取结果 + // Get results results, err := ga.GetResults() assert.NoError(t, err) assert.Len(t, results, 1) - // 由于表达式求值失败,聚合器应该没有值 + // Because the expression evaluation failed, the aggregator should have no value assert.Equal(t, "test_group", results[0]["group"]) - // processed_value 应该是聚合器的默认结果(通常是 nil 或 0) + // processed_value should be the default result of the aggregator (usually nil or 0) } -// TestGroupAggregatorCountStarField 测试 count(*) 功能 +// TestGroupAggregatorCountStarField tests count(*) functionality func TestGroupAggregatorCountStarField(t *testing.T) { ga := NewGroupAggregator( []string{"category"}, []AggregationField{ { - InputField: "*", // count(*) 语法 + InputField: "*", // count(*) syntax AggregateType: Count, OutputAlias: "total_count", }, }, ) - // 添加测试数据 + // Add test data testData := []map[string]any{ {"category": "A", "value": 10}, {"category": "A", "value": 20}, - {"category": "A"}, // 没有 value 字段 + {"category": "A"}, // There is no value field {"category": "B", "value": 30}, } @@ -1697,23 +1697,23 @@ func TestGroupAggregatorCountStarField(t *testing.T) { assert.NoError(t, err) } - // 获取结果 + // Get results results, err := ga.GetResults() assert.NoError(t, err) assert.Len(t, results, 2) - // 验证 count(*) 结果 + // Verify count(*) results for _, result := range results { category := result["category"].(string) if category == "A" { - assert.Equal(t, float64(3), result["total_count"]) // A 类别有 3 条记录 + assert.Equal(t, float64(3), result["total_count"]) // There are 3 records in category A } else if category == "B" { - assert.Equal(t, float64(1), result["total_count"]) // B 类别有 1 条记录 + assert.Equal(t, float64(1), result["total_count"]) // There is 1 record in category B } } } -// TestGroupAggregatorNilFieldValue 测试 nil 字段值处理 +// TestGroupAggregatorNilFieldValue tests nil field-value handling. func TestGroupAggregatorNilFieldValue(t *testing.T) { ga := NewGroupAggregator( []string{"group"}, @@ -1726,10 +1726,10 @@ func TestGroupAggregatorNilFieldValue(t *testing.T) { }, ) - // 添加包含 nil 值的数据 + // Add data containing the nil value testData := []map[string]any{ {"group": "test", "value": 10.0}, - {"group": "test", "value": nil}, // nil 值应该被跳过 + {"group": "test", "value": nil}, // The nil value should be skipped {"group": "test", "value": 20.0}, } @@ -1738,19 +1738,19 @@ func TestGroupAggregatorNilFieldValue(t *testing.T) { assert.NoError(t, err) } - // 获取结果 + // Get results results, err := ga.GetResults() assert.NoError(t, err) assert.Len(t, results, 1) - // nil 值应该被跳过,只计算 10.0 + 20.0 = 30.0 + // The nil value should be skipped, and only 10.0 + 20.0 = 30.0 is calculated assert.Equal(t, "test", results[0]["group"]) assert.Equal(t, 30.0, results[0]["sum_value"]) } -// TestGroupAggregatorStructData 测试结构体数据 +// TestGroupAggregatorStructData tests structure data func TestGroupAggregatorStructData(t *testing.T) { - // 定义测试结构体 + // Define the test structure type TestStruct struct { Group string Value float64 @@ -1767,7 +1767,7 @@ func TestGroupAggregatorStructData(t *testing.T) { }, ) - // 添加结构体数据 + // Add structure data testData := []TestStruct{ {Group: "A", Value: 10.0}, {Group: "A", Value: 20.0}, @@ -1779,12 +1779,12 @@ func TestGroupAggregatorStructData(t *testing.T) { assert.NoError(t, err) } - // 获取结果 + // Get results results, err := ga.GetResults() assert.NoError(t, err) assert.Len(t, results, 2) - // 验证结果 + // Verify the results for _, result := range results { group := result["Group"].(string) if group == "A" { @@ -1795,7 +1795,7 @@ func TestGroupAggregatorStructData(t *testing.T) { } } -// TestGroupAggregatorPointerData 测试指针数据 +// TestGroupAggregatorPointerData: Test pointer data func TestGroupAggregatorPointerData(t *testing.T) { type TestStruct struct { Group string @@ -1813,12 +1813,12 @@ func TestGroupAggregatorPointerData(t *testing.T) { }, ) - // 添加指针数据 + // Add pointer data testData := &TestStruct{Group: "test", Value: 42.0} err := ga.Add(testData) assert.NoError(t, err) - // 获取结果 + // Get results results, err := ga.GetResults() assert.NoError(t, err) assert.Len(t, results, 1) @@ -1826,7 +1826,7 @@ func TestGroupAggregatorPointerData(t *testing.T) { assert.Equal(t, 42.0, results[0]["sum_value"]) } -// TestGroupAggregatorUnsupportedDataType 测试不支持的数据类型 +// TestGroupAggregatorUnsupportedDataType Tests data types that are not supported func TestGroupAggregatorUnsupportedDataType(t *testing.T) { ga := NewGroupAggregator( []string{"group"}, @@ -1839,21 +1839,21 @@ func TestGroupAggregatorUnsupportedDataType(t *testing.T) { }, ) - // 测试不支持的数据类型 - err := ga.Add(123) // int 类型不是 struct 或 map + // Test for data types that are not supported + err := ga.Add(123) // The int type is not struct or map assert.Error(t, err) assert.Contains(t, err.Error(), "unsupported data type") - err = ga.Add("string") // string 类型不支持 + err = ga.Add("string") // string type is not supported assert.Error(t, err) assert.Contains(t, err.Error(), "unsupported data type") - err = ga.Add([]int{1, 2, 3}) // slice 类型不支持 + err = ga.Add([]int{1, 2, 3}) // The slice type is not supported assert.Error(t, err) assert.Contains(t, err.Error(), "unsupported data type") } -// TestGroupAggregatorGroupFieldNilValue 测试分组字段为 nil 时归入 NULL 分组(不丢行) +// TestGroupAggregatorGroupFieldNilValue When the test grouping field is nil, it is assigned to a NULL group (no line dropped) func TestGroupAggregatorGroupFieldNilValue(t *testing.T) { ga := NewGroupAggregator( []string{"group"}, @@ -1866,7 +1866,7 @@ func TestGroupAggregatorGroupFieldNilValue(t *testing.T) { }, ) - // 分组字段为 nil:归入 NULL 分组,不再报错丢行 + // Group field nil: Includes NULL grouping, no more errors or line drops err := ga.Add(map[string]any{ "group": nil, "value": 10.0, @@ -1880,15 +1880,15 @@ func TestGroupAggregatorGroupFieldNilValue(t *testing.T) { } } -// TestIsNumericAggregatorAdvanced 测试 isNumericAggregator 的更多分支 +// TestIsNumericAggregatorAdvanced covers additional isNumericAggregator branches func TestIsNumericAggregatorAdvanced(t *testing.T) { ga := NewGroupAggregator([]string{"group"}, []AggregationField{}) - // 测试 TypeAnalytical 类型 + // Test TypeAnalytical type result := ga.isNumericAggregator("analytical_func") assert.False(t, result) - // 测试不存在的函数,但名称包含数值聚合关键字 + // Tests functions that do not exist, but whose names contain numerical aggregation keywords result = ga.isNumericAggregator("custom_sum_func") assert.True(t, result) @@ -1910,12 +1910,12 @@ func TestIsNumericAggregatorAdvanced(t *testing.T) { result = ga.isNumericAggregator("custom_var_func") assert.True(t, result) - // 测试不包含数值关键字的函数 + // Tests functions that do not contain numeric keywords result = ga.isNumericAggregator("custom_text_func") assert.False(t, result) } -// TestGroupAggregatorNilData 测试 nil 数据 +// TestGroupAggregatorNilData tests nil data func TestGroupAggregatorNilData(t *testing.T) { ga := NewGroupAggregator( []string{"group"}, @@ -1928,13 +1928,13 @@ func TestGroupAggregatorNilData(t *testing.T) { }, ) - // 测试 nil 数据 + // Test NIL data err := ga.Add(nil) assert.Error(t, err) assert.Contains(t, err.Error(), "data cannot be nil") } -// TestGroupAggregatorMissingGroupField 测试缺少分组字段时归入 NULL 分组(不丢行) +// TestGroupAggregatorMissingGroupField When a group field is missing, NULL grouping is added (no line dropped) func TestGroupAggregatorMissingGroupField(t *testing.T) { ga := NewGroupAggregator( []string{"missing_group"}, @@ -1947,7 +1947,7 @@ func TestGroupAggregatorMissingGroupField(t *testing.T) { }, ) - // 缺少分组字段:归入 NULL 分组,不再报错丢行 + // Missing grouping fields: Includes NULL grouping, no longer throws errors or missing lines err := ga.Add(map[string]any{ "value": 10, }) @@ -1960,9 +1960,9 @@ func TestGroupAggregatorMissingGroupField(t *testing.T) { } } -// TestGroupAggregatorMissingAggregationField 测试缺少聚合字段但有上下文 +// TestGroupAggregatorMissingAggregationField The test lacks an aggregation field but has context func TestGroupAggregatorMissingAggregationField(t *testing.T) { - // 创建一个不会从上下文获取值的聚合器 + // Create an aggregator that does not retrieve values from context ga := NewGroupAggregator( []string{"group"}, []AggregationField{ @@ -1974,21 +1974,21 @@ func TestGroupAggregatorMissingAggregationField(t *testing.T) { }, ) - // 添加缺少聚合字段的数据(没有上下文) + // Add data missing aggregated fields (without context) err := ga.Add(map[string]any{ "group": "test", - // 缺少 missing_field + // Lack of missing_field }) - assert.NoError(t, err) // 应该成功,因为会跳过缺少的字段 + assert.NoError(t, err) // It should succeed, because missing fields will be skipped - // 获取结果 + // Get results results, err := ga.GetResults() assert.NoError(t, err) assert.Len(t, results, 1) - // 由于没有添加任何值,聚合器应该返回默认值 + // Since no value is added, the aggregator should return the default value } -// TestGroupAggregatorExpressionEvaluationError 测试表达式求值错误但继续处理 +// TestGroupAggregatorExpressionEvaluationError The test expression is evaluated incorrectly but continues to be processed func TestGroupAggregatorExpressionEvaluationError(t *testing.T) { ga := NewGroupAggregator( []string{"group"}, @@ -2006,23 +2006,23 @@ func TestGroupAggregatorExpressionEvaluationError(t *testing.T) { }, ) - // 注册一个会出错的表达式求值器 + // Register an error expression evaluator ga.RegisterExpression("expr_result", "error_expr", []string{"other"}, func(data any) (any, error) { return nil, fmt.Errorf("evaluation error") }) - // 添加数据 + // Add data err := ga.Add(map[string]any{ "group": "test", "value": 10, "other": 20, }) - assert.NoError(t, err) // 应该成功,因为表达式错误会被跳过 + assert.NoError(t, err) // It should succeed because an expression error can be skipped - // 获取结果 + // Get results results, err := ga.GetResults() assert.NoError(t, err) assert.Len(t, results, 1) - // sum_value 应该有值,expr_result 应该没有值或为默认值 + // sum_value should have a value, expr_result should have no value or be the default value assert.Equal(t, float64(10), results[0]["sum_value"]) } diff --git a/aggregator/post_aggregation.go b/aggregator/post_aggregation.go index 43df2a7..1df3eb8 100644 --- a/aggregator/post_aggregation.go +++ b/aggregator/post_aggregation.go @@ -33,14 +33,14 @@ var ( // PostAggregationExpression represents an expression that needs to be evaluated after aggregation type PostAggregationExpression struct { - OutputField string // 输出字段名 - Expression string // 表达式模板,如 "__first_value_0__ - __last_value_1__" - RequiredAggFields []string // 依赖的聚合字段,如 ["__first_value_0__", "__last_value_1__"] - OriginalExpr string // 原始表达式,用于调试 - processor *PostAggregationProcessor // 处理器引用 + OutputField string // Output field name + Expression string // Expression templates, such as "__first_value_0__ - __last_value_1__" + RequiredAggFields []string // Dependent aggregated fields, such as ["__first_value_0__", "__last_value_1__"] + OriginalExpr string // Original expression, used for debugging + processor *PostAggregationProcessor // Processor reference } -// Evaluate 评估后聚合表达式 +// Evaluate the post-aggregated expression func (pae *PostAggregationExpression) Evaluate(data map[string]any) (any, error) { if pae == nil { return nil, fmt.Errorf("post-aggregation expression is nil") @@ -199,36 +199,36 @@ func (p *PostAggregationProcessor) unwrapNestedSlices(value any) any { // ParseComplexAggregationExpression parses expressions containing multiple aggregation functions // Returns the list of required aggregation fields and the expression template -// 该函数将包含聚合函数的复杂表达式分解为: -// 1. 后聚合表达式模板(聚合函数被占位符替换) -// 2. 需要预先计算的聚合字段信息列表 -// 3. 错误信息(如果解析失败) +// This function decomposes complex expressions containing aggregate functions as: +// 1. Post-aggregation expression template (aggregator replaced by placeholders) +// 2. A list of aggregated field information that needs to be precomputed +// 3. Error messages (if parsing fails) // -// 示例: +// Example: // -// 输入: "SUM(price) + AVG(quantity) * 2" -// 输出: 表达式模板 "__SUM_123__ + __AVG_456__ * 2" -// 聚合字段 [{FieldName: "__SUM_123__", FunctionName: "SUM", Arguments: ["price"]}, ...] +// Input: "SUM(price) + AVG(quantity) * 2" +// Output: Expression template "__SUM_123__ + __AVG_456__ * 2" +// Aggregated field [{FieldName: "__SUM_123__", FunctionName: "SUM", Arguments: ["price"]},...] func ParseComplexAggregationExpression(expr string) (aggFields []AggregationFieldInfo, exprTemplate string, err error) { exprTemplate = expr - // 使用递归方法解析嵌套函数调用 + // Parse nested function calls using recursive methods aggFields, exprTemplate = parseNestedFunctions(expr, make([]AggregationFieldInfo, 0)) return aggFields, exprTemplate, nil } -// parseNestedFunctions 递归解析嵌套函数调用 +// parseNestedFunctions recursively parse nested function calls func parseNestedFunctions(expr string, aggFields []AggregationFieldInfo) ([]AggregationFieldInfo, string) { return parseNestedFunctionsWithDepth(expr, aggFields, 0) } -// findFunctionCalls 查找表达式中的所有函数调用 +// findFunctionCalls Finds all function calls in an expression func findFunctionCalls(expr string) [][]int { return funcCallRegex.FindAllStringSubmatchIndex(expr, -1) } -// generatePlaceholder 为函数调用生成唯一占位符 +// generatePlaceholder generates a unique placeholder for the function call func generatePlaceholder(funcName, fullFuncCall string) string { callHash := uint32(0) for i := 0; i < len(fullFuncCall); i++ { @@ -237,7 +237,7 @@ func generatePlaceholder(funcName, fullFuncCall string) string { return PlaceholderPrefix + funcName + "_" + strconv.FormatUint(uint64(callHash), 10) + PlaceholderSuffix } -// parseNestedFunctionsWithDepth 递归解析嵌套函数调用,支持深度控制 +// parseNestedFunctionsWithDepth recursively parses nested function calls, supporting depth control func parseNestedFunctionsWithDepth(expr string, aggFields []AggregationFieldInfo, depth int) ([]AggregationFieldInfo, string) { if depth > MaxExpressionDepth { return aggFields, expr @@ -249,7 +249,7 @@ func parseNestedFunctionsWithDepth(expr string, aggFields []AggregationFieldInfo return aggFields, expr } - // 从右到左处理,避免索引偏移问题 + // Handle from right to left to avoid index offset issues for i := len(matches) - 1; i >= 0; i-- { match := matches[i] funcStart := match[0] @@ -303,15 +303,15 @@ func parseNestedFunctionsWithDepth(expr string, aggFields []AggregationFieldInfo return aggFields, expr } -// isTopLevelAggregationFunction 检查表达式是否是顶层的单一聚合函数调用 +// isTopLevelAggregationFunction checks whether the expression is a single aggregation function call at the top level func isTopLevelAggregationFunction(expr string) bool { - // 提取最外层的函数名 + // Extract the outermost function name funcName := extractOutermostFunctionName(expr) if funcName == "" { return false } - // 检查是否是聚合函数 + // Check if it is an aggregate function if fn, exists := functions.Get(funcName); exists { switch fn.GetType() { case functions.TypeAggregation, functions.TypeAnalytical, functions.TypeWindow: @@ -321,20 +321,20 @@ func isTopLevelAggregationFunction(expr string) bool { return false } -// extractOutermostFunctionName 提取最外层的函数名 +// extractOutermostFunctionName Extracts the outermost function name func extractOutermostFunctionName(expr string) string { expr = strings.TrimSpace(expr) - // 查找第一个左括号 + // Find the first left parenthesis parenIndex := strings.Index(expr, "(") if parenIndex == -1 { return "" } - // 提取函数名 + // Extract the function name funcName := strings.TrimSpace(expr[:parenIndex]) - // 检查函数名是否有效(只包含字母、数字、下划线) + // Check if the function name is valid (only letters, numbers, and underscores) for _, char := range funcName { if !((char >= 'a' && char <= 'z') || (char >= 'A' && char <= 'Z') || (char >= '0' && char <= '9') || char == '_') { @@ -345,7 +345,7 @@ func extractOutermostFunctionName(expr string) string { return funcName } -// findMatchingParen 找到匹配的右括号 +// findMatchingParen finds the right bracket of the match func findMatchingParen(s string, start int) int { if start >= len(s) || s[start] != '(' { return -1 @@ -363,16 +363,16 @@ func findMatchingParen(s string, start int) int { } } } - return -1 // 未找到匹配的右括号 + return -1 // No matching right bracket found } // AggregationFieldInfo holds information about an aggregation function in an expression type AggregationFieldInfo struct { - FuncName string // 函数名,如 "first_value" - InputField string // 输入字段,如 "displayNum" - Placeholder string // 占位符,如 "__first_value_0__" - AggType AggregateType // 聚合类型 - FullCall string // 完整函数调用,如 "NTH_VALUE(value, 2)" + FuncName string // Function name, such as "first_value" + InputField string // Input fields, such as "displayNum" + Placeholder string // Placeholders, such as "__first_value_0__" + AggType AggregateType // Types of aggregation + FullCall string // Full function calls, such as "NTH_VALUE(value, 2)" } // Enhanced GroupAggregator with post-aggregation support @@ -613,7 +613,7 @@ func (ega *EnhancedGroupAggregator) GetResults() ([]map[string]any, error) { } // createParameterizedAggregator creates aggregator with parameters for complex functions -// 使用新的接口方法替代硬编码实现 +// New interface methods replace hard-coded implementations func (ega *EnhancedGroupAggregator) createParameterizedAggregator(field AggregationFieldInfo) AggregatorFunction { // Parse function call to extract parameters args, err := ega.parseFunctionCall(field.FullCall) diff --git a/aggregator/post_aggregation_test.go b/aggregator/post_aggregation_test.go index 35a50d1..8ede8da 100644 --- a/aggregator/post_aggregation_test.go +++ b/aggregator/post_aggregation_test.go @@ -8,7 +8,7 @@ import ( "github.com/stretchr/testify/require" ) -// TestParseComplexAggregationExpression 测试复杂聚合表达式解析 +// TestParseComplexAggregationExpression tests the parsing of complex aggregate expressions func TestParseComplexAggregationExpression(t *testing.T) { tests := []struct { name string @@ -20,13 +20,13 @@ func TestParseComplexAggregationExpression(t *testing.T) { name: "简单聚合函数", expr: "SUM(value)", expectError: false, - expectedLen: 0, // 顶级聚合函数不会被替换 + expectedLen: 0, // Top-level aggregation functions will not be replaced }, { name: "复杂表达式", expr: "SUM(value) + AVG(price)", expectError: false, - expectedLen: 1, // 实际只解析出一个聚合函数 + expectedLen: 1, // In reality, only one aggregator function is parsed }, { name: "嵌套函数", @@ -58,7 +58,7 @@ func TestParseComplexAggregationExpression(t *testing.T) { } } -// TestExtractOutermostFunctionNameEdgeCases 测试extractOutermostFunctionName函数的边界情况 +// TestExtractOutermostFunctionNameEdgeCases tests the boundary state of the extractOutermostFunctionName function func TestExtractOutermostFunctionNameEdgeCases(t *testing.T) { tests := []struct { name string @@ -112,7 +112,7 @@ func TestExtractOutermostFunctionNameEdgeCases(t *testing.T) { } } -// TestAddPostAggregationExpressionErrorCases 测试AddPostAggregationExpression函数的错误情况 +// TestAddPostAggregationExpressionErrorCases tests for errors in the AddPostAggregationExpression function func TestAddPostAggregationExpressionErrorCases(t *testing.T) { groupFields := []string{"category"} aggFields := []AggregationField{ @@ -183,7 +183,7 @@ func TestAddPostAggregationExpressionErrorCases(t *testing.T) { } } -// TestIsTopLevelAggregationFunction 测试顶级聚合函数检测 +// TestIsTopLevelAggregationFunction tests top-level aggregation function detection func TestIsTopLevelAggregationFunction(t *testing.T) { tests := []struct { name string @@ -208,7 +208,7 @@ func TestIsTopLevelAggregationFunction(t *testing.T) { { name: "复杂表达式", expr: "SUM(a) + COUNT(b)", - expected: true, // 实际返回true + expected: true, // Actually returns true }, } @@ -220,7 +220,7 @@ func TestIsTopLevelAggregationFunction(t *testing.T) { } } -// TestExtractOutermostFunctionName 测试提取最外层函数名 +// TestExtractOutermostFunctionName Tests to extract the outermost function name func TestExtractOutermostFunctionName(t *testing.T) { tests := []struct { name string @@ -262,7 +262,7 @@ func TestExtractOutermostFunctionName(t *testing.T) { } } -// TestFindMatchingParen 测试查找匹配括号 +// TestFindMatchingParen tests to find matching parentheses func TestFindMatchingParen(t *testing.T) { tests := []struct { name string @@ -304,7 +304,7 @@ func TestFindMatchingParen(t *testing.T) { } } -// TestNewEnhancedGroupAggregator 测试增强型分组聚合器创建 +// TestNewEnhancedGroupAggregator tests the creation of an enhanced group aggregator func TestNewEnhancedGroupAggregator(t *testing.T) { groupFields := []string{"category"} aggFields := []AggregationField{ @@ -317,15 +317,15 @@ func TestNewEnhancedGroupAggregator(t *testing.T) { assert.NotNil(t, agg.postProcessor) } -// TestPostAggregationProcessor 测试后聚合处理器 +// TestPostAggregationProcessor Tests the aggregation processor func TestPostAggregationProcessor(t *testing.T) { processor := NewPostAggregationProcessor() require.NotNil(t, processor) - // 添加表达式 + // Add expressions processor.AddExpression("result", "__sum_0__ + __count_1__", []string{"__sum_0__", "__count_1__"}, "__sum_0__ + __count_1__") - // 测试处理结果 + // Test processing results results := []map[string]any{ { "__sum_0__": 100, @@ -338,12 +338,12 @@ func TestPostAggregationProcessor(t *testing.T) { assert.NoError(t, err) assert.Len(t, processedResults, 1) assert.Equal(t, 110, processedResults[0]["result"]) - // 中间字段应该被清理 + // The middle field should be cleaned up assert.NotContains(t, processedResults[0], "__sum_0__") assert.NotContains(t, processedResults[0], "__count_1__") } -// TestPostAggregationProcessor_ProcessResults 测试后聚合处理器的ProcessResults方法 +// TestPostAggregationProcessor_ProcessResults Test the processor's ProcessResults method func TestPostAggregationProcessor_ProcessResults(t *testing.T) { processor := NewPostAggregationProcessor() groupFields := []string{"category"} @@ -353,13 +353,13 @@ func TestPostAggregationProcessor_ProcessResults(t *testing.T) { agg := NewEnhancedGroupAggregator(groupFields, aggFields) require.NotNil(t, agg) - // 测试空结果 + // Test empty results emptyResults := []map[string]any{} processedEmpty, err := processor.ProcessResults(emptyResults) assert.NoError(t, err) assert.Empty(t, processedEmpty) - // 测试有数据的结果 + // Testing results with data results := []map[string]any{ {"category": "A", "sum_value": 100}, {"category": "B", "sum_value": 200}, @@ -369,7 +369,7 @@ func TestPostAggregationProcessor_ProcessResults(t *testing.T) { assert.Len(t, processedResults, 2) } -// TestEnhancedGroupAggregatorAddPostAggregationExpression 测试添加后聚合表达式 +// TestEnhancedGroupAggregatorAddPostAggregationExpression tests the aggregated expression after the addition is completed func TestEnhancedGroupAggregatorAddPostAggregationExpression(t *testing.T) { groupFields := []string{"category"} aggFields := []AggregationField{ @@ -379,7 +379,7 @@ func TestEnhancedGroupAggregatorAddPostAggregationExpression(t *testing.T) { agg := NewEnhancedGroupAggregator(groupFields, aggFields) require.NotNil(t, agg) - // 测试添加后聚合表达式 + // Test the added aggregate expression requiredFields := []AggregationFieldInfo{ { FuncName: "sum", @@ -401,7 +401,7 @@ func TestEnhancedGroupAggregatorAddPostAggregationExpression(t *testing.T) { assert.NoError(t, err) } -// TestEnhancedGroupAggregatorGetResults 测试获取增强聚合结果 +// TestEnhancedGroupAggregatorGetResults tests to obtain enhanced aggregated results func TestEnhancedGroupAggregatorGetResults(t *testing.T) { groupFields := []string{"category"} aggFields := []AggregationField{ @@ -412,7 +412,7 @@ func TestEnhancedGroupAggregatorGetResults(t *testing.T) { agg := NewEnhancedGroupAggregator(groupFields, aggFields) require.NotNil(t, agg) - // 添加测试数据 + // Add test data testData := []map[string]any{ {"category": "A", "value": 10}, {"category": "A", "value": 20}, @@ -423,13 +423,13 @@ func TestEnhancedGroupAggregatorGetResults(t *testing.T) { agg.Add(data) } - // 获取结果 + // Get results results, err := agg.GetResults() assert.NoError(t, err) - assert.Len(t, results, 2) // 两个分组 + assert.Len(t, results, 2) // Two groups } -// TestHasMultipleTopLevelArgs 测试检查函数是否有多个顶级参数 +// TestHasMultipleTopLevelArgs tests whether the function has multiple top-level parameters func TestHasMultipleTopLevelArgs(t *testing.T) { tests := []struct { name string @@ -476,7 +476,7 @@ func TestHasMultipleTopLevelArgs(t *testing.T) { } } -// TestParseFunctionCall 测试解析函数调用 +// TestParseFunctionCall is a call to test parsing functions func TestParseFunctionCall(t *testing.T) { groupFields := []string{"category"} aggFields := []AggregationField{ @@ -537,7 +537,7 @@ func TestParseFunctionCall(t *testing.T) { } } -// mockAggregatorFunction 实现AggregatorFunction接口用于测试 +// mockAggregatorFunction implements the AggregatorFunction interface for testing type mockAggregatorFunction struct { name string result any @@ -571,7 +571,7 @@ func (m *mockAggregatorFunction) Clone() functions.AggregatorFunction { } } -// 实现Function接口的其他方法 +// Other methods to implement the Function interface func (m *mockAggregatorFunction) GetName() string { if m.name != "" { return m.name @@ -729,41 +729,41 @@ func (m *mockAggregatorFunctionWithConfig) GetType() functions.FunctionType { return m.mockAggregatorFunction.GetType() } -// TestWindowFunctionWrapper 测试WindowFunctionWrapper的所有方法 +// TestWindowFunctionWrapper tests all methods of WindowFunctionWrapper func TestWindowFunctionWrapper(t *testing.T) { - // 创建一个mock的AggregatorFunction + // Create a mock AggregatorFunction mockAgg := &mockAggregatorFunction{result: 42.0} - // 创建WindowFunctionWrapper + // Create a WindowFunctionWrapper wrapper := &WindowFunctionWrapper{aggFunc: mockAgg} - // 测试New方法 + // Test the new method newWrapper := wrapper.New() assert.NotNil(t, newWrapper) assert.IsType(t, &WindowFunctionWrapper{}, newWrapper) - // 测试Add方法 + // Test the Add method wrapper.Add(10.0) assert.Len(t, mockAgg.values, 1) assert.Equal(t, 10.0, mockAgg.values[0]) - // 测试Result方法 + // Test the Result method result := wrapper.Result() assert.Equal(t, 42.0, result) - // 测试Reset方法 + // Test the reset method wrapper.Reset() assert.Nil(t, mockAgg.values) assert.Nil(t, mockAgg.result) - // 测试Clone方法 + // Testing the Clone method clonedWrapper := wrapper.Clone() assert.NotNil(t, clonedWrapper) assert.IsType(t, &WindowFunctionWrapper{}, clonedWrapper) assert.NotSame(t, wrapper, clonedWrapper) } -// TestCreateParameterizedAggregator 测试创建参数化聚合器 +// TestCreateParameterizedAggregator tests the creation of a parameterized aggregator func TestCreateParameterizedAggregator(t *testing.T) { groupFields := []string{"category"} aggFields := []AggregationField{ @@ -809,13 +809,13 @@ func TestCreateParameterizedAggregator(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { aggregator := agg.createParameterizedAggregator(tt.fieldInfo) - // 只验证返回值不为nil,因为具体实现可能返回nil + // Only verify that the return value is nil, because the actual implementation may return nil _ = aggregator }) } } -// TestPostAggregationComplexScenarios 测试复杂的后聚合场景 +// TestPostAggregationComplexScenarios tests complex post-aggregation scenarios func TestPostAggregationComplexScenarios(t *testing.T) { groupFields := []string{"category"} aggFields := []AggregationField{ @@ -826,7 +826,7 @@ func TestPostAggregationComplexScenarios(t *testing.T) { agg := NewEnhancedGroupAggregator(groupFields, aggFields) require.NotNil(t, agg) - // 添加后聚合表达式 + // Add the expression after aggregation requiredFields := []AggregationFieldInfo{ {FuncName: "SUM", InputField: "value", Placeholder: "sum_value", AggType: Sum}, {FuncName: "COUNT", InputField: "value", Placeholder: "count_value", AggType: Count}, @@ -834,7 +834,7 @@ func TestPostAggregationComplexScenarios(t *testing.T) { err := agg.AddPostAggregationExpression("avg_calc", "sum_value / count_value", requiredFields) assert.NoError(t, err) - // 添加测试数据 + // Add test data testData := []map[string]any{ {"category": "A", "value": 10.0}, {"category": "A", "value": 20.0}, @@ -847,32 +847,32 @@ func TestPostAggregationComplexScenarios(t *testing.T) { assert.NoError(t, err) } - // 获取结果 + // Get results results, err := agg.GetResults() assert.NoError(t, err) assert.NotEmpty(t, results) - // 验证结果数量 - assert.Len(t, results, 2) // 应该有两个分组结果 + // Verification of the number of results + assert.Len(t, results, 2) // There should be two grouping results - // 验证后聚合计算结果存在 + // After verification, the aggregated calculation results exist for _, result := range results { if category, ok := result["category"]; ok { assert.Contains(t, result, "sum_value") assert.Contains(t, result, "count_value") - // 验证基本的数据类型 + // Verify basic data types if category == "A" || category == "B" { assert.NotNil(t, result["sum_value"]) assert.NotNil(t, result["count_value"]) - // avg_calc可能不存在,因为后聚合处理可能需要特殊配置 - // 只验证基础聚合字段存在即可 + // avg_calc may not exist, as post-aggregation processing may require special configurations + // Only verify the existence of the basic aggregate field } } } } -// TestPerformanceOptimizations 测试性能优化相关功能 +// TestPerformanceOptimizations: Testing performance optimization related features func TestPerformanceOptimizations(t *testing.T) { t.Run("测试checkRequiredFields方法", func(t *testing.T) { processor := NewPostAggregationProcessor() @@ -884,11 +884,11 @@ func TestPerformanceOptimizations(t *testing.T) { "__avg_price_placeholder_456__": 50.0, } - // 测试所有字段都存在的情况 + // Test the situation where all fields are present allPresent := processor.checkRequiredFields(result, requiredFields) assert.True(t, allPresent) - // 测试缺少字段的情况 + // Test the missing field incompleteResult := map[string]any{ "__sum_amount_placeholder_123__": 100.0, } @@ -923,42 +923,42 @@ func TestPerformanceOptimizations(t *testing.T) { t.Run("测试fieldsCache缓存功能", func(t *testing.T) { processor := NewPostAggregationProcessor() - // 添加表达式,测试缓存 + // Add expressions and test cache requiredFields := []string{"__sum_amount_placeholder_123__"} processor.AddExpression("expr1", "sum(amount)", requiredFields, "__sum_amount_placeholder_123__") processor.AddExpression("expr2", "sum(amount)", requiredFields, "__sum_amount_placeholder_123__") - // 验证缓存中有对应的字段信息 + // Verify that the cache contains the corresponding field information assert.NotEmpty(t, processor.fieldsCache) assert.Contains(t, processor.fieldsCache, "expr1") assert.Contains(t, processor.fieldsCache, "expr2") }) t.Run("测试正则表达式缓存", func(t *testing.T) { - // 验证全局正则表达式已编译 + // Verify that the global regular expression has been compiled assert.NotNil(t, funcCallRegex) assert.NotNil(t, placeholderRegex) - // 测试funcCallRegex + // Testing funcCallRegex matches := funcCallRegex.FindAllStringSubmatchIndex("sum(amount)", -1) assert.NotEmpty(t, matches) - // 测试placeholderRegex + // Test placeholderRegex placeholderMatches := placeholderRegex.FindAllStringSubmatch("__sum_amount_placeholder_123__", -1) assert.NotEmpty(t, placeholderMatches) }) } -// TestProcessResultsPerformance 测试ProcessResults方法的性能优化 +// TestProcessResultsPerformance Optimization of the ProcessResults method func TestProcessResultsPerformance(t *testing.T) { processor := NewPostAggregationProcessor() - // 添加多个表达式 + // Add multiple expressions processor.AddExpression("calc1", "sum(amount) * 2", []string{"__sum_amount_placeholder_123__"}, "__sum_amount_placeholder_123__ * 2") processor.AddExpression("calc2", "avg(price) + 10", []string{"__avg_price_placeholder_456__"}, "__avg_price_placeholder_456__ + 10") processor.AddExpression("calc3", "max(value) - min(value)", []string{"__max_value_placeholder_789__", "__min_value_placeholder_012__"}, "__max_value_placeholder_789__ - __min_value_placeholder_012__") - // 创建大量测试数据 + // Creating a large amount of test data results := make([]map[string]any, 100) for i := 0; i < 100; i++ { results[i] = map[string]any{ @@ -969,23 +969,23 @@ func TestProcessResultsPerformance(t *testing.T) { } } - // 处理结果并验证 + // Process the results and verify them processedResults, err := processor.ProcessResults(results) assert.NoError(t, err) assert.Len(t, processedResults, 100) - // 验证第一个结果 + // Verify the first result assert.Equal(t, 0.0, processedResults[0]["calc1"]) // 0 * 2 = 0 assert.Equal(t, 10.0, processedResults[0]["calc2"]) // 0 + 10 = 10 assert.Equal(t, 0.0, processedResults[0]["calc3"]) // 0 - 0 = 0 - // 验证最后一个结果 + // Verify the last result lastIdx := len(processedResults) - 1 assert.Equal(t, 1980.0, processedResults[lastIdx]["calc1"]) // 99*10*2 = 1980 assert.Equal(t, 505.0, processedResults[lastIdx]["calc2"]) // 99*5+10 = 505 assert.Equal(t, 1881.0, processedResults[lastIdx]["calc3"]) // 99*20-99 = 1881 - // 验证占位符字段已被清理 + // The verification placeholder field has been cleaned up for _, result := range processedResults { assert.NotContains(t, result, "__sum_amount_placeholder_123__") assert.NotContains(t, result, "__avg_price_placeholder_456__") diff --git a/cep/engine.go b/cep/engine.go index 4b01054..bf5d41d 100644 --- a/cep/engine.go +++ b/cep/engine.go @@ -13,34 +13,34 @@ import ( "github.com/rulego/streamsql/types" ) -// 默认有界参数(边缘内存保护)。 +// Default bounded parameters (edge memory protection). const ( - defaultMaxPartitions = 10000 // 分区数上限(超出 LRU 淘汰) - defaultMaxRunRows = 10000 // 单次部分匹配的最大行数(行长上限) - defaultMaxRuns = 10000 // 单分区的活跃部分匹配数上限(数量上限,防 A* 类状态爆炸) + defaultMaxPartitions = 10000 // Partition limit (exceeding LRU elimination) + defaultMaxRunRows = 10000 // Maximum number of rows per partial match (line length limit) + defaultMaxRuns = 10000 // Maximum number of matches for active portions in a single partition (maximum quantity, prevents A* status explosion) maxInt64 int64 = 1<<63 - 1 ) -// Engine 是一个 MATCH_RECOGNIZE 查询的 CEP 引擎:按分区维护 NFA 运行态,逐事件推进, -// 匹配完成时产出 MEASURES 投影行。线程安全(Process 串行;分区 LRU)。 +// Engine is a CEP engine for MATCH_RECOGNIZE queries: maintaining NFA runtime by partition and advancing event by event, +// Produces the MEASURES projection row upon matching. Thread safety (Process serialization; Partition LRU). type Engine struct { nfa *NFA spec *types.MatchRecognizeSpec symbols map[string]bool - subsets map[string][]string // SUBSET 名 → 成员符号(求值期按成员集合过滤) - lazy bool // pattern 含 reluctant 量词:立即 emit 选最短;否则贪婪延迟选最长 + subsets map[string][]string // SUBSET Name → Member Symbol (Evaluate by member set) + lazy bool // pattern contains reluctant quantifiers: immediately emit choose the shortest; Otherwise, choose the longest delay for greed measures []types.Measure - // DEFINE/MEASURES 表达式预编译产物(NewEngine 期一次编译,热路径复用)。 + // DEFINE/MEASURES expression precompilation product (NewEngine phase one-time compilation, hotpath multiplexing). definePrep map[string]*preparedExpr measurePrep []*preparedExpr - tsField string // ORDER BY 首字段,用作事件时间戳 + tsField string // ORDER BY first field, used as the event timestamp within time.Duration maxRunRows int maxRuns int - // WITHIN 主动过期 sweeper(仅 within>0 时由 Start 启动;Stop join)。 + // WITHIN Actively Expired Sweeper (only starts with Start when within>0 is used; Stop join). startMu sync.Mutex started bool ctx context.Context @@ -50,24 +50,24 @@ type Engine struct { maxPart int mu sync.Mutex - partMap map[string]*list.Element // 分区 LRU + partMap map[string]*list.Element // Partitioned LRU lru *list.List - seq int64 // 全局单调到达序号(SKIP 起点跟踪用) + seq int64 // Global Monotone Arrival Number (SKIP for starting point tracking) - log logger.Logger // 求值诊断日志器(per-engine,消除包级竞争) - errOnce sync.Map // key: where+"\x00"+src,每表达式仅记一次求值失败(per-engine 有界) + log logger.Logger // Evaluate diagnostic loggers (per-engine, eliminate packet-level contention) + errOnce sync.Map // key: where+"\x00"+src, each expression only records one evaluation failure (per-engine is bounded) } type partition struct { key string runs []*run - pending map[int64][]*run // 贪婪:已完成 run 按 startSeq 暂存,等延伸终止选最长 emit - matchNo int // 本分区已输出匹配数(MATCH_NUMBER) - nextStart int64 // 下一个允许起匹配的 seq(SKIP 策略) + pending map[int64][]*run // Greed: Run completed, press startSeq to temporarily store, wait for extension termination and select the longest emit + matchNo int // Number of matches output in this partition (MATCH_NUMBER) + nextStart int64 // Next to allow matching seq (SKIP strategy) } -// frame 是匹配历史的不可变节点(cons-list):advance 仅 O(1) 追加,前缀天然共享, -// 消除旧实现里每后继全量复制 rows/labels 的 O(N²) 开销。 +// frame is an immutable node (cons-list) that matches history: advance only adds O(1), and prefixes are naturally shared, +// Eliminates the O(N²) overhead of coping rows/labels in the old implementation every successor. type frame struct { row map[string]any label string @@ -75,15 +75,15 @@ type frame struct { } type run struct { - states []*state // 当前 epsilon 闭包状态集 - head *frame // 最近一行(链头);nil 表示空匹配 + states []*state // The current epsilon closure state set + head *frame // Nearest line (chain head); nil stands for empty match nrows int startTs int64 startSeq int64 - matchNo int // 输出时分配 + matchNo int // Distribution at output } -// materialize 把 cons-list 反向展开为 rows+labels 切片(O(N),仅在求值/投影时按需调用)。 +// materialize unfolds cons-list backward into rows+labels slices (O(N), called only as needed during evaluation/projection). func (r *run) materialize() ([]map[string]any, []string) { rows := make([]map[string]any, r.nrows) labels := make([]string, r.nrows) @@ -94,7 +94,7 @@ func (r *run) materialize() ([]map[string]any, []string) { return rows, labels } -// Validate 校验 spec 可编译(模式树、ORDER BY、DEFINE/MEASURES 表达式)。供 Execute 期 fail-fast。 +// Validate Validation specs can be compiled (schema tree, ORDER BY, DEFINE/MEASURES expressions). Allows the Execute period to fail-fast. func Validate(spec *types.MatchRecognizeSpec) error { if spec == nil { return errPatternRequired @@ -112,8 +112,8 @@ func Validate(spec *types.MatchRecognizeSpec) error { if _, err := Compile(pattern); err != nil { return err } - // 表达式预编译校验:语法错的 DEFINE/MEASURES 在 Execute 即暴露, - // 而非运行期静默按不匹配/空值处理(仅一次节流日志,难定位)。 + // Expression precompilation verification: Syntax errors DEFINE/MEASURES are exposed in Execute, + // Non-runtime silence is treated as mismatch/null (only one throttling log, difficult to locate). for _, d := range spec.Defines { if strings.TrimSpace(d.Cond) == "" { continue @@ -133,7 +133,7 @@ func Validate(spec *types.MatchRecognizeSpec) error { return nil } -// NewEngine 编译 spec 为 NFA 并构建引擎,预编译全部 DEFINE/MEASURES 表达式。 +// NewEngine compiles specs as NFA and builds the engine, precompiling all DEFINE/MEASURES expressions. func NewEngine(spec *types.MatchRecognizeSpec) (*Engine, error) { if spec == nil || spec.Pattern == nil { return nil, errPatternRequired @@ -191,40 +191,40 @@ func NewEngine(spec *types.MatchRecognizeSpec) (*Engine, error) { } e.sweepInterval = e.within / 2 if e.sweepInterval < 50*time.Millisecond { - e.sweepInterval = 50 * time.Millisecond // 下限:防极小 WITHIN 产生过密 ticker 占 CPU + e.sweepInterval = 50 * time.Millisecond // Lower limit: Prevents extremely small WITHIN from generating overly dense tickers that occupy CPU } return e, nil } -// SetMaxPartitions 覆盖分区上限。 +// SetMaxPartitions override the partition limit. func (e *Engine) SetMaxPartitions(n int) { if n > 0 { e.maxPart = n } } -// SetMaxRunRows 覆盖单次部分匹配行数上限。 +// SetMaxRunRows overrides the maximum number of rows in a single partial match. func (e *Engine) SetMaxRunRows(n int) { if n > 0 { e.maxRunRows = n } } -// SetMaxRuns 覆盖单分区活跃部分匹配数上限。 +// SetMaxRuns covers the maximum number of matches for active parts of a single partition. func (e *Engine) SetMaxRuns(n int) { if n > 0 { e.maxRuns = n } } -// SetLogger 覆盖求值诊断日志器(per-engine,避免包级共享导致多 Stream 互相覆盖/竞争)。 +// SetLogger overrides the evaluation diagnostic logger (per-engine, preventing packet-level sharing that could overwrite or compete with multiple streams). func (e *Engine) SetLogger(l logger.Logger) { if l != nil { e.log = l } } -// logEvalErr 首次记录某表达式求值失败(per-engine 去重,条目数受该引擎表达式数有界)。 +// logEvalErr records the first instance of an expression evaluation failure (per-engine deduplication, entry count is bounded by the number of expressions in that engine). func (e *Engine) logEvalErr(where, src string, err error) { if err == nil || e.log == nil { return @@ -232,11 +232,11 @@ func (e *Engine) logEvalErr(where, src string, err error) { if _, dup := e.errOnce.LoadOrStore(where+"\x00"+src, true); dup { return } - e.log.Error("CEP %s 表达式求值失败,按不匹配/空值处理(同类后续不再重复): %q: %v", where, src, err) + e.log.Error("CEP %s Expression evaluation fails, treated as mismatch/null (similar type will not repeat later): %q: %v", where, src, err) } -// Flush 冲刷所有分区中「已可接受但未终结」的部分匹配(如流末未界的 A+ 突发)。 -// 适配器在 Stop 时调用,避免流末未闭合的匹配丢失。返回冲刷出的输出行。 +// Flush flushes some 'acceptable but not yet finished' matches in all partitions (such as the A+ burst at the end of the flow). +// The adapter is called on Stop to prevent loss of matches at the end of the stream. Returns the output line that was flushed. func (e *Engine) Flush() []map[string]any { e.mu.Lock() defer e.mu.Unlock() @@ -255,7 +255,7 @@ func (e *Engine) Flush() []map[string]any { } continue } - // 贪婪:并入 pending(只留最长),流末 survivors 空 → 全部 ready emit。 + // Greed: Merge into pending (keep only the longest part), end of the stream survivors empty → all ready emit. e.ingestPending(p, completions) if out := e.emitGreedy(p, &[]*run{}); len(out) > 0 { emitted = append(emitted, out...) @@ -264,8 +264,8 @@ func (e *Engine) Flush() []map[string]any { return emitted } -// Start 启动 WITHIN 主动过期 sweeper(仅 within>0 时)。幂等。 -// sweeper 定期用 wall-clock 扫描,清掉超窗的部分匹配,避免空闲分区内存滞留。 +// Start WITHIN to activate the active expired sweeper (only when within>0). Power equal. +// The sweeper periodically scans with a wall-clock to clear some matches from the overwindow, preventing memory retention in idle partitions. func (e *Engine) Start() { e.startMu.Lock() defer e.startMu.Unlock() @@ -292,8 +292,8 @@ func (e *Engine) sweepLoop() { } } -// sweep 清除超窗的部分匹配。仅对 epoch 量级时间戳用 wall-clock 判过期: -// 小值序号流(非真实时间戳)维持纯被动,避免 wall-clock 误删。 +// sweep to clear partial matches in the superwindow. Only epoch-scale timestamps are marked as expired using wall-clock: +// Small-value sequence number flow (not real timestamp) remains purely passive to avoid wall-clock accidental deletion. func (e *Engine) sweep() { if e.within <= 0 { return @@ -303,21 +303,21 @@ func (e *Engine) sweep() { e.mu.Lock() for el := e.lru.Front(); el != nil; el = el.Next() { p := el.Value.(*partition) - kept := p.runs[:0] // 原地过滤:写 index ≤ 读 index,无删除时不分配 + kept := p.runs[:0] // In-place filtering: writes index ≤ reads index; no assignment is made if not deleted for _, r := range p.runs { if r.startTs >= int64(1e9) && now-r.startTs > limit { - continue // 超窗的延伸中 run:丢弃 + continue // In the extension of the superwindow, run: discard } kept = append(kept, r) } p.runs = kept - // pending(已完成的合法匹配)不由 sweep 清理:sweep 用 wall-clock、withinOk 用事件时间, - // 清 pending 会误杀事件时间窗内的合法匹配。pending 仅由 emitGreedy/Flush 产出、capPending 限界。 + // pending (completed valid matches) is not cleaned by sweep: sweep uses wall-clock, withinOk uses event time, + // Clearing pending will falsely kill legal matches within the event time window. Pending is only produced by emitGreedy/Flush and capPending limits. } e.mu.Unlock() } -// Stop 停止 sweeper 并 join。幂等;未 Start 时直接返回。 +// Stop the sweeper and join. idempotent; Returns directly when not started. func (e *Engine) Stop() { e.startMu.Lock() if !e.started { @@ -331,7 +331,7 @@ func (e *Engine) Stop() { e.wg.Wait() } -// Process 投入一行事件到其分区,返回本事件完成的匹配经 MEASURES 投影后的输出行。 +// Process feeds a line of event to its partition, returning the output line of the match completed by this event after the MEASURES projection. func (e *Engine) Process(row map[string]any, partitionKey string) []map[string]any { if row == nil { return nil @@ -370,20 +370,20 @@ func (e *Engine) evictIfNeeded() { } } -// step 推进一个分区的全部 run,处理完成匹配(SKIP 策略)与种子。 +// step Advance all runs of a partition to complete matching (SKIP strategy) with seeds. func (e *Engine) step(p *partition, row map[string]any, ts, seq int64) []map[string]any { var survivors []*run var completions []*run - // 1. 推进现有 run(含未界完成:mr 不属于但 run 已可接受)。 + // 1. Advance existing runs (including unbounded completion: mr is not included but run is acceptable). for _, r := range p.runs { if !e.withinOk(r, ts) || r.nrows > e.maxRunRows { - continue // 超期/超长:丢弃 + continue // Overdue/Overtime: Discarded } succ := e.advance(r, row) if len(succ) == 0 { if hasAccept(r.states) { - completions = append(completions, r) // 未界重复(A+/A*)收尾 + completions = append(completions, r) // Unbounded repeat (A+/A*) for the finish } continue } @@ -396,7 +396,7 @@ func (e *Engine) step(p *partition, row map[string]any, ts, seq int64) []map[str } } - // 2. 种子:从起点尝试以本事件起始(受 nextStart 限制)。 + // 2. Seed: Try to start from the starting point with this event (restricted by nextStart). if seq >= p.nextStart { seed := &run{states: closure(e.nfa.start), startTs: ts, startSeq: seq} for _, s := range e.advance(seed, row) { @@ -408,7 +408,7 @@ func (e *Engine) step(p *partition, row map[string]any, ts, seq int64) []map[str } } - // 3. 处理完成匹配:懒惰立即 emit 选最短;贪婪暂存 pending,等延伸终止选最长。 + // 3. After processing and matching: Lazy immediately emit and select the shortest option; Greedy Temporary Storage Pending, wait for extension termination and choose the longest option. var emitted []map[string]any if e.lazy { emitted = e.emitLazy(p, completions, &survivors) @@ -416,18 +416,18 @@ func (e *Engine) step(p *partition, row map[string]any, ts, seq int64) []map[str e.ingestPending(p, completions) emitted = e.emitGreedy(p, &survivors) } - // 4. 数量上限:活跃部分匹配过多时丢弃最旧的,防 A* 类模式状态爆炸。 + // 4. Quantity limit: If the active group matches too much, the oldest one is discarded to prevent A* mode status explosions. if e.maxRuns > 0 && len(survivors) > e.maxRuns { excess := len(survivors) - e.maxRuns survivors = survivors[excess:] } - e.capPending(p) // pending key 数上限:防贪婪延迟期无界累积 + e.capPending(p) // Pending key limit: Unlimited accumulation during anti-greed delay period p.runs = survivors return emitted } -// advance 测试 row 能否被 run 当前闭包里的各 match-state 消费,返回全部后继 run(非确定性)。 -// 仅在符号的 DEFINE 含历史引用(PREV/聚合/符号字段)时才展开历史,否则 O(1)。 +// advance tests whether the row can be consumed by the match-states in the current closure, returning all subsequent runs (non-deterministic). +// History is only expanded when the symbol DEFINE contains historical references (PREV/aggregation/symbol fields); otherwise, O(1). func (e *Engine) advance(r *run, row map[string]any) []*run { var out []*run seen := make(map[*state]bool) @@ -459,7 +459,7 @@ func (e *Engine) advance(r *run, row map[string]any) []*run { return out } -// evalDefine 求值符号的预编译 DEFINE 条件(布尔)。空条件恒真。 +// precompilation of evalDefine evaluation symbols DEFINE condition (Boolean). The empty condition is always true. func (e *Engine) evalDefine(p *preparedExpr, buffer []map[string]any, labels []string, candidate map[string]any, candLabel string) bool { if p == nil || p.compiled == nil { return true @@ -476,7 +476,7 @@ func (e *Engine) evalDefine(p *preparedExpr, buffer []map[string]any, labels []s return truthy(v) } -// evalMeasure 求值预编译 MEASURES 表达式(值)。 +// evalMeasure evaluates the precompiled MEASURES expression (value). func (e *Engine) evalMeasure(p *preparedExpr, rows []map[string]any, labels []string, cur, matchNumber int) (any, bool) { ctx := &matchCtx{rows: rows, labels: labels, cur: cur, symbols: e.symbols, subsets: e.subsets, matchNumber: matchNumber} v, isNull, err := evalPrepared(p, ctx) @@ -487,8 +487,8 @@ func (e *Engine) evalMeasure(p *preparedExpr, rows []map[string]any, labels []st return v, isNull } -// emitOne 产出一次匹配:分配 MATCH_NUMBER、投影、按 SKIP 设 nextStart、裁剪 survivors。 -// emitLazy/emitGreedy 共用,保证两条路径输出主干一致。 +// emitOne outputs a single match: assign MATCH_NUMBER, project, set nextStart by SKIP, crop survivors. +// emitLazy/emitGreedy are shared to ensure the output backbone of both paths is consistent. func (e *Engine) emitOne(p *partition, c *run, survivors *[]*run) []map[string]any { p.matchNo++ c.matchNo = p.matchNo @@ -498,7 +498,7 @@ func (e *Engine) emitOne(p *partition, c *run, survivors *[]*run) []map[string]a return out } -// emitLazy 处理懒惰模式的完成匹配:立即按 startSeq 升序、同 startSeq 选最短 emit。 +// emitLazy handles the completion matching of lazy mode: immediately ascend by startSeq and select the shortest emit with startSeq. func (e *Engine) emitLazy(p *partition, completions []*run, survivors *[]*run) []map[string]any { if len(completions) == 0 { return nil @@ -507,7 +507,7 @@ func (e *Engine) emitLazy(p *partition, completions []*run, survivors *[]*run) [ if completions[i].startSeq != completions[j].startSeq { return completions[i].startSeq < completions[j].startSeq } - return completions[i].nrows < completions[j].nrows // 懒惰:同 startSeq 选最短 + return completions[i].nrows < completions[j].nrows // Laziness: Use startSeq to choose the shortest option }) var emitted []map[string]any for _, c := range completions { @@ -519,11 +519,11 @@ func (e *Engine) emitLazy(p *partition, completions []*run, survivors *[]*run) [ return emitted } -// emitGreedy 处理贪婪模式的完成匹配:pending 已按 startSeq 暂存(只留最长),emit 延伸 -// 终止的 startSeq(survivors 中无同 startSeq 的 run)。survivors 为空时 emit 全部(供 Flush)。 +// emitGreedy handles the completion match for greedy mode: pending has been temporarily stored with startSeq (leaving only the longest time), emit extends +// Terminated startSeq (there is no run with the same startSeq in survivors). survivors are empty when all are emit (for Flush). func (e *Engine) emitGreedy(p *partition, survivors *[]*run) []map[string]any { if len(p.pending) == 0 { - return nil // 默认贪婪模式每事件调用:无在途匹配时短路,避免无用 map 分配 + return nil // By default, greedy mode calls per event: short-circuited when no in-transit matching occurs, avoiding useless map allocation } active := make(map[int64]bool, len(*survivors)) for _, r := range *survivors { @@ -539,7 +539,7 @@ func (e *Engine) emitGreedy(p *partition, survivors *[]*run) []map[string]any { var emitted []map[string]any for _, s := range ready { if s < p.nextStart { - continue // 被前一轮 SKIP 推进跳过(直接守卫,与 emitLazy 一致) + continue // Skipped by the previous SKIP push (direct guard, same as emitLazy) } best := p.pending[s][0] emitted = append(emitted, e.emitOne(p, best, survivors)...) @@ -549,7 +549,7 @@ func (e *Engine) emitGreedy(p *partition, survivors *[]*run) []map[string]any { return emitted } -// prunePending 清除 startSeq < nextStart 的暂存完成匹配(已被 SKIP 跳过)。 +// prunePending clears the startSeq < nextStart temporary completion match (skipped by SKIP). func (e *Engine) prunePending(p *partition, nextStart int64) { for s := range p.pending { if s < nextStart { @@ -558,8 +558,8 @@ func (e *Engine) prunePending(p *partition, nextStart int64) { } } -// ingestPending 把 completion 入队 pending(贪婪):每 startSeq 只保留最长 completion -// (emitGreedy 最终选最长,短的无需保留),供 step 与 Flush 共用。 +// ingestPending enqueues completion pending: Each startSeq only retains the longest completion +// (emitGreedy finally selects the longest part, shortest option is not kept), for use by step and Flush for sharing. func (e *Engine) ingestPending(p *partition, completions []*run) { for _, c := range completions { if c.startSeq < p.nextStart { @@ -572,8 +572,8 @@ func (e *Engine) ingestPending(p *partition, completions []*run) { } } -// capPending 限制 pending key 数(未 emit 的 startSeq 数),超限时丢弃最旧 startSeq。 -// 贪婪延迟 emit 期 startSeq 可能累积,此为内存保护(牺牲最旧匹配换内存有界)。 +// capPending limits the number of pending keys (the number of startSeq not emitted); if it exceeds the limit, the oldest startSeq is discarded. +// Greedy delay and emit periods may accumulate startSeq during this period, which is memory protection (sacrificing the oldest match for a bounded memory). func (e *Engine) capPending(p *partition) { if e.maxRuns <= 0 { return @@ -589,7 +589,7 @@ func (e *Engine) capPending(p *partition) { } } -// skipTo 返回该匹配完成后下一个允许起匹配的 seq(依 SKIP 策略)。 +// skipTo returns the next seq that allows matching after the match is completed (according to the SKIP policy). func (e *Engine) skipTo(c *run) int64 { endSeq := c.startSeq + int64(c.nrows) - 1 switch e.spec.Skip { @@ -603,7 +603,7 @@ func (e *Engine) skipTo(c *run) int64 { return endSeq + 1 } -// seqOfLabel 返回匹配中某标签(或 SUBSET 的任一成分)首/末出现行的全局 seq。 +// seqOfLabel returns the global seq for the first and last lines of a tag (or any component of the SUBSET) in the match. func seqOfLabel(c *run, label string, first bool, subsets map[string][]string) int64 { if label == "" { return -1 @@ -613,9 +613,9 @@ func seqOfLabel(c *run, label string, first bool, subsets map[string][]string) i for f := c.head; f != nil; f, i = f.prev, i-1 { if labelMatches(f.label, label, subsets) { if !first { - return c.startSeq + int64(i) // LAST:最近(最先从 head 遇到) + return c.startSeq + int64(i) // LAST: Recently (first encountered from head) } - idx = i // FIRST:记最小 i,走到最老 + idx = i // FIRST: Remember the smallest i, walk the oldest one } } if idx < 0 { @@ -634,7 +634,7 @@ func (e *Engine) pruneSurvivors(survivors *[]*run, nextStart int64) { *survivors = kept } -// project 把一次完成匹配投影为输出行:ONE ROW 在末行求值;ALL ROWS 逐行(RUNNING)求值。 +// project projects a single completed matching projection as the output line: ONE ROW evaluates on the last row; ALL ROWS RUNNINGs for evaluation. func (e *Engine) project(c *run) []map[string]any { rows, labels := c.materialize() if e.spec.RowsPerMatch == types.RowsPerMatchAll { @@ -650,10 +650,10 @@ func (e *Engine) project(c *run) []map[string]any { return []map[string]any{e.evalMeasures(c, rows, labels, len(rows)-1)} } -// evalMeasures 投影一次匹配的输出行(对齐 Flink MATCH_RECOGNIZE 关系语义): -// - 无 MEASURES:输出当前行字段副本(ONE ROW=末行)。 -// - ONE ROW PER MATCH:仅 MEASURES 别名(关系只暴露 MEASURES 列)。 -// - ALL ROWS PER MATCH:输入行字段 + MEASURES 别名(同名以 MEASURES 为准)。 +// evalMeasures projects a matching output line (aligning Flink MATCH_RECOGNIZE relational semantics): +// - No MEASURES: Outputs a copy of the current row field (ONE ROW = last line). +// - ONE ROW PER MATCH: Only MEASURES aliases (the relationship only exposes the MEASURES column). +// - ALL ROWS PER MATCH: Enter row field + MEASURES aliases (MEASURES for the same name). func (e *Engine) evalMeasures(c *run, rows []map[string]any, labels []string, cur int) map[string]any { row := rows[cur] if len(e.measures) == 0 { @@ -683,10 +683,10 @@ func copyRow(r map[string]any) map[string]any { return out } -// withinOk 报告 run 是否仍在 WITHIN 时窗内。 -// 限制:WITHIN 为被动检查——仅在事件到达推进 run 时判定,无主动定时器。 -// 故空闲分区的部分匹配不会因超时即时清除,需等下一事件到达或分区 LRU 淘汰。 -// (内存仍受 maxRuns×maxRunRows×maxPartitions 有界,非泄漏。) +// withinOk reports whether run is still within the WITHIN window. +// Limitation: WITHIN is a passive check—only determined when the event arrives at the advance run, with no active timer. +// Therefore, partial matches in idle partitions will not be cleared immediately due to timeout; they must wait for the next event or partition LRU to be eliminated. +// (Memory is still bounded by maxRuns×maxRunRows×maxPartitions and is not leaked.)) func (e *Engine) withinOk(r *run, curTs int64) bool { if e.within <= 0 { return true @@ -694,7 +694,7 @@ func (e *Engine) withinOk(r *run, curTs int64) bool { return curTs-r.startTs <= e.within.Nanoseconds() } -// collectSymbols 收集模式中全部模式变量名。 +// collectSymbols: All variable names in the collection mode. func collectSymbols(spec *types.MatchRecognizeSpec) map[string]bool { m := make(map[string]bool) if spec.Pattern != nil { @@ -716,9 +716,9 @@ func walkSymbols(n *types.PatternNode, m map[string]bool) { } } -// hasReluctant 报告模式树是否含懒惰量词(Quantifier.Greedy=false)。 -// 整体近似:只要含任意 reluctant 量词,整引擎走 emitLazy(同 startSeq 选最短); -// 混合贪婪/懒惰量词的模式不保证逐量词优先级(纯贪婪/纯懒惰正确)。 +// hasReluctant reports whether the pattern tree contains a laziness quantifier (Quantifier.Greedy=false). +// Overall approximation: As long as any reluctant quantifier is included, the entire engine runs emitLazy (same as startSeq with the shortest option); +// The mixed greed/laziness quantifier pattern does not guarantee per-quantifier priority (pure greed/pure laziness is correct). func hasReluctant(n *types.PatternNode) bool { if n == nil { return false @@ -734,7 +734,7 @@ func hasReluctant(n *types.PatternNode) bool { return false } -// collectSubsets 收集 SUBSET 名 → 成员符号列表。 +// collectSubsets Collects a list of SUBSET names → member symbols. func collectSubsets(spec *types.MatchRecognizeSpec) map[string][]string { m := make(map[string][]string, len(spec.Subsets)) for _, s := range spec.Subsets { @@ -743,14 +743,14 @@ func collectSubsets(spec *types.MatchRecognizeSpec) map[string][]string { return m } -// maxSubsetMembers 限制单个 SUBSET 的成员数,防止展开成交替后 NFA 状态膨胀。 +// maxSubsetMembers limits the number of members in a single SUBSET to prevent NFA state expansion after expansion and trade. const maxSubsetMembers = 8 -// expandSubsets 把模式树中的 SUBSET 名(PatternLiteral)展开为其成员的交替: -// PATTERN(S)(S={A,B})→ PATTERN(A|B)。展开后 match-state 携带真实成分符号, -// CLASSIFIER() 返回成分而非 SUBSET 名(SQL 标准)。 -// 传入的 subsets 已扁平化(flattenMembers)、通过环检测(subsetHasCycle)与成员数校验 -// (resolveSymbols),成员均为真实模式变量,无需递归或防环。 +// expandSubsets expands the SUBSET name (PatternLiteral) in the pattern tree into the alternation of its members: +// PATTERN(S) (S={A,B})→ PATTERN(A| B). After expansion, match-state carries the true component symbol, +// CLASSIFIER() returns the component instead of the SUBSET name (SQL standard). +// Incoming subsets have been flattened (flattenMembers), passed loop detection (subsetHasCycle), and member count validation +// (resolveSymbols), all members are real pattern variables, no need for recursion or anti-looping. func expandSubsets(n *types.PatternNode, subsets map[string][]string) *types.PatternNode { return expandSubsetsRec(n, subsets) } @@ -762,7 +762,7 @@ func expandSubsetsRec(n *types.PatternNode, subsets map[string][]string) *types. if n.Kind == types.PatternLiteral { members, ok := subsets[n.Symbol] if !ok { - return n // 普通符号:原样 + return n // Regular symbols: as is } children := make([]*types.PatternNode, 0, len(members)) for _, m := range members { @@ -773,7 +773,7 @@ func expandSubsetsRec(n *types.PatternNode, subsets map[string][]string) *types. } return &types.PatternNode{Kind: types.PatternAlternation, Children: children} } - out := *n // 浅拷贝,不改原树 + out := *n // Shallow copying, no original tree changes if len(n.Children) > 0 { out.Children = make([]*types.PatternNode, len(n.Children)) for i, c := range n.Children { @@ -783,24 +783,24 @@ func expandSubsetsRec(n *types.PatternNode, subsets map[string][]string) *types. return &out } -// resolveSymbols 展开 SUBSET、合并符号集,供 NewEngine/Validate 共用。 -// 返回:prepare 用的符号集(PATTERN literal ∪ DEFINE 声明 ∪ SUBSET 名)、SUBSET 成员表 -// (已扁平化为真实模式变量,求值期等值比较即可)、展开后的 pattern。 +// resolveSymbols expands the SUBSET and merge the symbol set for sharing by NewEngine/Validate. +// Returns: the symbol set for prepare (PATTERN literal ∪ DEFINE declares ∪ SUBSET name), and the SUBSET member table +// (Flattened to the real model variable, just compare the values during the evaluation period) The expanded pattern. func resolveSymbols(spec *types.MatchRecognizeSpec) (map[string]bool, map[string][]string, *types.PatternNode, error) { subsets := collectSubsets(spec) - // 已知符号 = PATTERN literal ∪ DEFINE 声明 ∪ SUBSET 名。 + // Given symbol = PATTERN literal ∪ DEFINE declares ∪ SUBSET name. known := collectSymbols(spec) for _, d := range spec.Defines { known[d.Symbol] = true } - // 无 SUBSET:跳过扁平化/展开/校验与全树深拷贝,pattern 原样返回。 + // No SUBSET: Skips flattening/expanding/validation and full-tree deep copy; returns pattern as is. if len(subsets) == 0 { return known, nil, spec.Pattern, nil } for name := range subsets { known[name] = true } - // 成员校验:必须是已知符号,且成员数受 maxSubsetMembers 限制(防 NFA 交替膨胀)。 + // Member validation: must be a known symbol, and the number of members is limited by maxSubsetMembers (to prevent NFA alternating bloat). for name, members := range subsets { if len(members) > maxSubsetMembers { return nil, nil, nil, fmt.Errorf("SUBSET %q has too many members (%d > %d)", name, len(members), maxSubsetMembers) @@ -811,13 +811,13 @@ func resolveSymbols(spec *types.MatchRecognizeSpec) (map[string]bool, map[string } } } - // 环检测:SUBSET 依赖图不得有环。 + // Ring detection: SUBSET dependency graphs must not have rings. for name := range subsets { if subsetHasCycle(name, subsets, map[string]bool{}) { return nil, nil, nil, fmt.Errorf("SUBSET %q has a cyclic definition", name) } } - // 扁平化:把成员里的 SUBSET 名递归展开为真实模式变量,求值期无需再递归。 + // Flattening: Revising the SUBSET names in members into real pattern variables, so no further recursion is needed during evaluation. flat := make(map[string][]string, len(subsets)) for name, members := range subsets { flat[name] = flattenMembers(members, subsets) @@ -825,7 +825,7 @@ func resolveSymbols(spec *types.MatchRecognizeSpec) (map[string]bool, map[string return known, flat, expandSubsets(spec.Pattern, flat), nil } -// subsetHasCycle 沿 SUBSET 依赖图 DFS 检测环(path 回溯)。 +// subsetHasCycle runs along the SUBSET dependency diagram DFS detection loop (path backtracking). func subsetHasCycle(name string, subsets map[string][]string, path map[string]bool) bool { if path[name] { return true @@ -840,7 +840,7 @@ func subsetHasCycle(name string, subsets map[string][]string, path map[string]bo return false } -// flattenMembers 把成员里的 SUBSET 名递归展开为真实模式变量(去重;已通过环检测)。 +// flattenMembers recursively expands the SUBSET names in the member into the real pattern variable (deduplication; Passed ring detection). func flattenMembers(members []string, subsets map[string][]string) []string { var out []string seen := make(map[string]bool, len(members)) @@ -859,33 +859,33 @@ func flattenMembers(members []string, subsets map[string][]string) []string { return out } -// normalizeTs 把事件时间戳归一化为纳秒(自动判单位:ns/μs/ms/s epoch)。 -// 注意:按数值量级猜单位,边界值(如跨 1e9)可能误判;建议用真实 epoch 时间戳, -// 或同一条流内单位一致。相对小序号(<1e9)按原值比较,WITHIN 须同单位。 -// 乘法前做溢出钳制,避免大时间戳回绕为负破坏 WITHIN 判定。 +// normalizeTs normalizes event timestamps to nanoseconds (automatic units: ns/μs/ms/s epoch). +// Note: Guessing units based on numerical magnitude may result in misjudgment of boundary values (such as crossing 1e9); It is recommended to use a real epoch timestamp, +// Or the same unit within the same flow. For smaller sequence numbers (<1e9), compare according to the original value; WITHIN must be the same unit. +// Perform overflow clamping before multiplication to avoid large timestamp looping and negative damage WITHIN determination. func normalizeTs(v int64) int64 { switch { case v <= 0: return 0 case v >= 1e18: return v - case v >= 1e15: // 微秒 + case v >= 1e15: // Microseconds if v > maxInt64/1000 { return maxInt64 } return v * 1000 - case v >= 1e12: // 毫秒 + case v >= 1e12: // Milliseconds if v > maxInt64/1000000 { return maxInt64 } return v * 1000000 - case v >= 1e9: // 秒 + case v >= 1e9: // seconds if v > maxInt64/1000000000 { return maxInt64 } return v * 1000000000 } - return v // 小值(相对序号):原样,WITHIN 按同单位比较 + return v // Small value (relative number): As is, WITHIN is compared with the same unit } func toInt64(v any) int64 { @@ -904,7 +904,7 @@ func toInt64(v any) int64 { return 0 } -// 预定义错误(避免在热路径里 fmt)。 +// Predefined errors (avoid FMT in hot paths). var ( errPatternRequired = cepError("MATCH_RECOGNIZE requires a PATTERN clause") errOrderByRequired = cepError("MATCH_RECOGNIZE requires ORDER BY (provides event ordering)") diff --git a/cep/engine_test.go b/cep/engine_test.go index 920d0b9..bd3c5a3 100644 --- a/cep/engine_test.go +++ b/cep/engine_test.go @@ -7,7 +7,7 @@ import ( "github.com/rulego/streamsql/types" ) -// --- 测试辅助:模式树构造 + 引擎运行 --- +// --- Test Assistance: Pattern Tree Construction + Engine Running --- func lit(s string) *types.PatternNode { return &types.PatternNode{Kind: types.PatternLiteral, Symbol: s} @@ -22,11 +22,11 @@ func rep(c *types.PatternNode, min, max int) *types.PatternNode { return &types.PatternNode{Kind: types.PatternRepetition, Children: []*types.PatternNode{c}, Quant: &types.Quantifier{Min: min, Max: max, Greedy: true}} } -func def(symbol, cond string) types.MatchDefine { return types.MatchDefine{Symbol: symbol, Cond: cond} } -func measure(expr, alias string) types.Measure { return types.Measure{Expr: expr, Alias: alias} } -func orderBy(field string) []types.OrderByField { return []types.OrderByField{{Expression: field}} } +func def(symbol, cond string) types.MatchDefine { return types.MatchDefine{Symbol: symbol, Cond: cond} } +func measure(expr, alias string) types.Measure { return types.Measure{Expr: expr, Alias: alias} } +func orderBy(field string) []types.OrderByField { return []types.OrderByField{{Expression: field}} } -// runEvents 建引擎并按序投入事件,收集全部输出行(含 Flush)。 +// runEvents builds the engine and installs events sequentially, collecting all output rows (including Flush). func runEvents(t *testing.T, spec *types.MatchRecognizeSpec, rows []map[string]any) []map[string]any { t.Helper() e, err := NewEngine(spec) @@ -53,9 +53,9 @@ func asFloat(v any) float64 { return 0 } -// --- 场景测试(端到端,从场景推导,不从代码反推)--- +// --- Scenario testing (end-to-end, deriving from scenarios, not backward from code)--- -// 场景1:A{3} 连续越限确认(防抖)。 +// Scenario 1: A{3} Continuous limit overreach confirmation (anti-shake). func TestScenario1_ConsecutiveThreshold(t *testing.T) { spec := &types.MatchRecognizeSpec{ Pattern: rep(lit("A"), 3, 3), @@ -78,7 +78,7 @@ func TestScenario1_ConsecutiveThreshold(t *testing.T) { } } -// 场景2:A B 过热后回落(故障前兆)。 +// Scenario 2: A and B overheat and then fall back (a warning sign). func TestScenario2_RiseThenDrop(t *testing.T) { spec := &types.MatchRecognizeSpec{ Pattern: seq(lit("A"), lit("B")), @@ -93,7 +93,7 @@ func TestScenario2_RiseThenDrop(t *testing.T) { } } -// 场景3:A B+ C 单调上升后转降(PREV + 聚合 MEASURES)。 +// Scenario 3: A B+ C monotonically rises and then falls (PREV + aggregate MEASURES). func TestScenario3_TrendReversal(t *testing.T) { spec := &types.MatchRecognizeSpec{ Pattern: seq(lit("A"), rep(lit("B"), 1, -1), lit("C")), @@ -113,7 +113,7 @@ func TestScenario3_TrendReversal(t *testing.T) { } } -// 场景4:A{5,} 振动突发(5+,以中断事件收尾)。 +// Scenario 4: A{5,} Vibration Burst (5+, ending with an interrupt event). func TestScenario4_VibrationBurst(t *testing.T) { spec := &types.MatchRecognizeSpec{ Pattern: rep(lit("A"), 5, -1), @@ -132,7 +132,7 @@ func TestScenario4_VibrationBurst(t *testing.T) { } } -// 场景5:Start Process+ End 跨事件类型序列(工作流)。 +// Scenario 5: Start Process+ End cross-event type sequence (workflow). func TestScenario5_CrossEventSequence(t *testing.T) { spec := &types.MatchRecognizeSpec{ Pattern: seq(lit("Start"), rep(lit("Process"), 1, -1), lit("End")), @@ -151,9 +151,9 @@ func TestScenario5_CrossEventSequence(t *testing.T) { } } -// --- 量词 / 交替 / PERMUTE --- +// --- Measure Words / Alternation / PERMUTE --- -// A? B(A 可选):A,B → n=2;无 A 直接 B → n=1。 +// A? B(A) optional: A, B → n=2; without A, direct B→n=1. func TestQuantifier_Optional(t *testing.T) { spec := &types.MatchRecognizeSpec{ Pattern: seq(rep(lit("A"), 0, 1), lit("B")), @@ -168,7 +168,7 @@ func TestQuantifier_Optional(t *testing.T) { } } -// A | B 交替 + CLASSIFIER。 +// A | B alternates + CLASSIFIER. func TestAlternation(t *testing.T) { spec := &types.MatchRecognizeSpec{ Pattern: altNode(lit("A"), lit("B")), @@ -183,12 +183,12 @@ func TestAlternation(t *testing.T) { } } -// PERMUTE(A, B):两种顺序都匹配。 +// PERMUTE(A, B): matches both sequences. func TestPermute(t *testing.T) { spec := &types.MatchRecognizeSpec{ - Pattern: &types.PatternNode{Kind: types.PatternPermute, Children: []*types.PatternNode{lit("A"), lit("B")}}, - Defines: []types.MatchDefine{def("A", "k == 1"), def("B", "k == 2")}, - OrderBy: orderBy("ts"), + Pattern: &types.PatternNode{Kind: types.PatternPermute, Children: []*types.PatternNode{lit("A"), lit("B")}}, + Defines: []types.MatchDefine{def("A", "k == 1"), def("B", "k == 2")}, + OrderBy: orderBy("ts"), Measures: []types.Measure{measure("CLASSIFIER()", "last")}, } rows := []map[string]any{{"ts": 1, "k": 1}, {"ts": 2, "k": 2}, {"ts": 3, "k": 2}, {"ts": 4, "k": 1}} @@ -198,9 +198,9 @@ func TestPermute(t *testing.T) { } } -// --- 导航 --- +// --- Navigation --- -// NEXT 在末行位置越界返回 nil。 +// NEXT crosses the boundary at the last line and returns nil. func TestNextNavigation(t *testing.T) { spec := &types.MatchRecognizeSpec{ Pattern: seq(lit("A"), lit("B")), @@ -215,7 +215,7 @@ func TestNextNavigation(t *testing.T) { } } -// --- WITHIN(单次匹配时间上界 + 过期重置)--- +// --- WITHIN (single match time upper bound + expired reset)--- func TestWithinExpiry(t *testing.T) { spec := &types.MatchRecognizeSpec{ @@ -243,7 +243,7 @@ func TestWithinOK(t *testing.T) { } } -// WITHIN 过期后恢复:第一对超窗作废,第二对在窗内仍匹配。 +// WITHIN expires and restored: the first pair of super windows is voided, while the second pair still matches within the window. func TestWithinResetRecovery(t *testing.T) { const base = int64(1700000000000) spec := &types.MatchRecognizeSpec{ @@ -254,9 +254,9 @@ func TestWithinResetRecovery(t *testing.T) { } rows := []map[string]any{ {"ts": base, "k": 1}, // A - {"ts": base + 70000, "k": 2}, // 70s > 1m → 过期 - {"ts": base + 100000, "k": 1}, // 新 A - {"ts": base + 100030, "k": 2}, // 30ms < 1m → 匹配 + {"ts": base + 70000, "k": 2}, // 70s > 1m → expired + {"ts": base + 100000, "k": 1}, // New A + {"ts": base + 100030, "k": 2}, // 30ms < 1m → matching } out := runEvents(t, spec, rows) if len(out) != 1 || asFloat(out[0]["mn"]) != 1 { @@ -264,9 +264,9 @@ func TestWithinResetRecovery(t *testing.T) { } } -// --- 分区 / 有界 --- +// --- Zoning / Bounded --- -// 分区隔离:不同分区各自独立匹配。 +// Partition isolation: Different partitions match independently. func TestPartitionIsolation(t *testing.T) { spec := &types.MatchRecognizeSpec{ Pattern: seq(lit("A"), lit("B")), @@ -286,7 +286,7 @@ func TestPartitionIsolation(t *testing.T) { } } -// maxRuns 上限:A* 类状态爆炸被截断,不崩溃。 +// maxRuns limit: Class A* state explosions are interrupted but do not crash. func TestMaxRunsCap(t *testing.T) { spec := &types.MatchRecognizeSpec{ Pattern: seq(rep(lit("A"), 0, -1), lit("B")), @@ -300,10 +300,10 @@ func TestMaxRunsCap(t *testing.T) { for i := 0; i < 10000; i++ { e.Process(map[string]any{"ts": int64(i), "k": 1}, "") } - e.Flush() // 不崩溃即通过 + e.Flush() // If you don't collapse, you pass } -// --- Validate(构造期 fail-fast)--- +// --- Validate (fail-fast)--- func TestValidateExclusionRejected(t *testing.T) { spec := &types.MatchRecognizeSpec{ @@ -324,7 +324,7 @@ func TestValidateMissing(t *testing.T) { } } -// 结构畸形的 DEFINE/MEASURES 表达式(括号不配平)被 Validate 拒绝(fail-fast)。 +// Structurally malformed DEFINE/MEASURES expressions (parentheses not even) are rejected by Validate (fail-fast). func TestValidateMalformedExpr(t *testing.T) { spec := &types.MatchRecognizeSpec{ Pattern: lit("A"), @@ -344,9 +344,9 @@ func TestValidateMalformedExpr(t *testing.T) { } } -// --- NULL 传播 --- +// --- NULL Spread --- -// 单符号 DEFINE 用 PREV:首行 PREV=nil → 比较为假 → 不匹配。 +// For single-symbol DEFINE with PREV: first line PREV=nil → compared to false → mismatch. func TestNullInDefine(t *testing.T) { spec := &types.MatchRecognizeSpec{ Pattern: lit("A"), @@ -362,7 +362,7 @@ func TestNullInDefine(t *testing.T) { // --- SUBSET --- -// SUBSET 在 MEASURES 引用:SUM(S.v) 对 S={A,B} 全部成分行求和;S.v 取成分末行。 +// SUBSET in MEASURES reference: SUM(S.v) sums all components of S={A,B}; S.v takes the last row of the components. func TestSubset_Measures(t *testing.T) { spec := &types.MatchRecognizeSpec{ Pattern: seq(lit("A"), rep(lit("B"), 1, -1)), @@ -374,38 +374,38 @@ func TestSubset_Measures(t *testing.T) { }, } rows := []map[string]any{ - {"ts": 1, "k": 1, "v": 1}, // A - {"ts": 2, "k": 2, "v": 10}, // B + {"ts": 1, "k": 1, "v": 1}, // A + {"ts": 2, "k": 2, "v": 10}, // B {"ts": 3, "k": 2, "v": 100}, // B - {"ts": 4, "k": 3, "v": 0}, // 非 A/B:收尾 B+ + {"ts": 4, "k": 3, "v": 0}, // Non-A/B: Finishing B+ } out := runEvents(t, spec, rows) if len(out) != 1 { t.Fatalf("want 1 match, got %d: %v", len(out), out) } - if asFloat(out[0]["sv"]) != 111 { // S={A,B} 全部:1+10+100 + if asFloat(out[0]["sv"]) != 111 { // S = {A, B} All: 1+10+100 t.Errorf("SUM(S.v)=%v want 111", out[0]["sv"]) } - if asFloat(out[0]["av"]) != 1 { // 只 A + if asFloat(out[0]["av"]) != 1 { // Only A t.Errorf("SUM(A.v)=%v want 1", out[0]["av"]) } - if asFloat(out[0]["last"]) != 100 { // S 成分末行 = 最后 B + if asFloat(out[0]["last"]) != 100 { // S component last row = last B t.Errorf("S.v=%v want 100", out[0]["last"]) } } -// SUBSET 在 PATTERN 里作原子:PATTERN(S C)(S={A,B})→ (A|B) C,match-state 携带真实成分。 +// SUBSET is an atom in a PATTERN: PATTERN(S C)(S={A,B})→ (A| B) C. match-state carries the real ingredients. func TestSubset_InPattern(t *testing.T) { spec := &types.MatchRecognizeSpec{ - Pattern: seq(lit("S"), lit("C")), - Subsets: []types.MatchSubset{{Name: "S", Symbols: []string{"A", "B"}}}, - Defines: []types.MatchDefine{def("A", "k == 1"), def("B", "k == 2"), def("C", "k == 3")}, - OrderBy: orderBy("ts"), + Pattern: seq(lit("S"), lit("C")), + Subsets: []types.MatchSubset{{Name: "S", Symbols: []string{"A", "B"}}}, + Defines: []types.MatchDefine{def("A", "k == 1"), def("B", "k == 2"), def("C", "k == 3")}, + OrderBy: orderBy("ts"), RowsPerMatch: types.RowsPerMatchAll, - Measures: []types.Measure{measure("CLASSIFIER()", "c")}, + Measures: []types.Measure{measure("CLASSIFIER()", "c")}, } rows := []map[string]any{ - {"ts": 1, "k": 1}, // A(经 S 展开匹配) + {"ts": 1, "k": 1}, // A (Expand S for Matching) {"ts": 2, "k": 3}, // C } out := runEvents(t, spec, rows) @@ -413,14 +413,14 @@ func TestSubset_InPattern(t *testing.T) { t.Fatalf("want 2 rows (A,C), got %d: %v", len(out), out) } if out[0]["c"] != "A" { - t.Errorf("row0 classifier=%v want A (S 成分)", out[0]["c"]) + t.Errorf("row0 classifier=%v want A (S components)", out[0]["c"]) } if out[1]["c"] != "C" { t.Errorf("row1 classifier=%v want C", out[1]["c"]) } } -// 嵌套 SUBSET:S2=(S1, C)、S1=(A, B)。SUM(S2.v) 覆盖 A/B/C 全部。 +// Nested SUBSET: S2=(S1, C), S1=(A, B). SUM(S2.v) covers all A/B/C components. func TestSubset_Nested(t *testing.T) { spec := &types.MatchRecognizeSpec{ Pattern: seq(lit("A"), lit("B"), lit("C")), @@ -443,11 +443,11 @@ func TestSubset_Nested(t *testing.T) { } } -// Validate 拒绝引用未知符号的 SUBSET。 +// Validate refuses to reference SUBSETS of unknown symbols. func TestSubset_ValidateUnknownMember(t *testing.T) { spec := &types.MatchRecognizeSpec{ Pattern: lit("A"), - Subsets: []types.MatchSubset{{Name: "S", Symbols: []string{"A", "X"}}}, // X 未知 + Subsets: []types.MatchSubset{{Name: "S", Symbols: []string{"A", "X"}}}, // X Unknown OrderBy: orderBy("ts"), } if err := Validate(spec); err == nil { @@ -455,13 +455,13 @@ func TestSubset_ValidateUnknownMember(t *testing.T) { } } -// Validate 拒绝循环 SUBSET 定义。 +// Validate rejects the SUBSET loop definition. func TestSubset_ValidateCycle(t *testing.T) { spec := &types.MatchRecognizeSpec{ Pattern: seq(lit("A"), lit("B")), Subsets: []types.MatchSubset{ {Name: "S1", Symbols: []string{"A", "S2"}}, - {Name: "S2", Symbols: []string{"B", "S1"}}, // S1↔S2 环 + {Name: "S2", Symbols: []string{"B", "S1"}}, // S1↔S2 ring }, OrderBy: orderBy("ts"), } @@ -470,7 +470,7 @@ func TestSubset_ValidateCycle(t *testing.T) { } } -// 贪婪 A*(DEFINE 重叠)选最长 [A,A,B];懒惰 A*? 选最短 [B]×3。 +// Greedy A* (DEFINE overlap) chooses the longest [A,A,B]; Lazy A*? Choose the shortest [B]×3. func TestQuantifier_GreedyVsReluctant(t *testing.T) { star := func(greedy bool) *types.PatternNode { return &types.PatternNode{Kind: types.PatternRepetition, Children: []*types.PatternNode{lit("A")}, Quant: &types.Quantifier{Min: 0, Max: -1, Greedy: greedy}} @@ -484,12 +484,12 @@ func TestQuantifier_GreedyVsReluctant(t *testing.T) { } } rows := []map[string]any{{"ts": 1, "v": 1}, {"ts": 2, "v": 2}, {"ts": 3, "v": 3}} - // 贪婪:延伸到流末 Flush 选最长 → [A,A,B] 1 个匹配 n=3。 + // Greed: Extend to the end of the flow, Flush chooses the longest → [A,A,B] 1 match n=3. gOut := runEvents(t, mk(true), rows) if len(gOut) != 1 || asFloat(gOut[0]["n"]) != 3 { t.Fatalf("greedy A* want 1 match n=3, got %v", gOut) } - // 懒惰:每位置立即选最短 → [B]×3。 + // Laziness: Immediately select the shortest → [B]×3 per position. lOut := runEvents(t, mk(false), rows) if len(lOut) != 3 { t.Fatalf("reluctant A*? want 3 matches, got %d: %v", len(lOut), lOut) @@ -501,7 +501,7 @@ func TestQuantifier_GreedyVsReluctant(t *testing.T) { } } -// SUBSET 成员数超 maxSubsetMembers(8)在编译期报错(而非静默截断)。 +// SUBSET Members exceed maxSubsetMembers (8) Error at compile time (not silence truncation). func TestSubset_ValidateTooManyMembers(t *testing.T) { spec := &types.MatchRecognizeSpec{ Pattern: lit("A"), diff --git a/cep/eval.go b/cep/eval.go index 011d86a..3262589 100644 --- a/cep/eval.go +++ b/cep/eval.go @@ -1,13 +1,13 @@ -// Package cep 实现 MATCH_RECOGNIZE 模式识别(SQL:2016)。 +// Package cep implements MATCH_RECOGNIZE pattern recognition (SQL:2016). // -// 架构: -// - pattern.go 组合式模式树 → NFA(Thompson 构造),支持序列/量词/选择/分组/PERMUTE。 -// - nfa.go NFA 状态机 + epsilon 闭包。 -// - eval.go DEFINE/MEASURES 求值:表达式在 NewEngine 期预编译(prepare), -// 导航/聚合/符号限定字段改写为占位符,复用手写 expr 引擎求值。 -// - engine.go 分区 NFA 模拟 + LRU + WITHIN/行数上限(有界)+ MEASURES 投影 + 匹配输出。 +// Structure: +// - pattern.go Composable schema tree → NFA (Thompson construct), supporting sequences/quantifiers/selection/grouping/PERMUTE. +// - nfa.go NFA State Machine + epsilon closure. +// - eval.go DEFINE/MEASURES Evaluation: The expression is precompiled during the NewEngine phase, +// Navigation/aggregation/symbol constraint fields are rewritten as placeholders, and the handwritten expr engine is used for evaluation. +// - engine.go Partition NFA Analog + LRU + WITHIN/ Row Limit (Bounded) + MEASURES Projection + Match Output. // -// CEP 是独立子系统,不依赖也不污染现有直连/窗口/分析路径与三套求值器。 +// CEP is an independent subsystem that neither depends on nor contaminates existing direct/window/analysis paths and three sets of evaluators. package cep import ( @@ -21,9 +21,9 @@ import ( "github.com/rulego/streamsql/utils/cast" ) -// matchCtx 是 DEFINE/MEASURES 求值的上下文:一次匹配尝试的全部行 + 分类标签。 -// DEFINE 求值时 candidate=待分类行、cur=len(rows)(candidate 概念上在 cur 位置); -// MEASURES 求值时 candidate=nil、cur=求值行下标(ONE ROW=末行)。 +// matchCtx is the context for DEFINE/MEASURES evaluation: all rows + classification labels for a single match attempt. +// When evaluating DEFINE, candidate = row to be classified, cur = len(rows) (candidate is conceptually at the cur position); +// When evaluating MEASURES, candidate=nil, cur=inscript of the evaluated line (ONE ROW = last line). type matchCtx struct { rows []map[string]any labels []string @@ -31,14 +31,14 @@ type matchCtx struct { candidate map[string]any candLabel string symbols map[string]bool - subsets map[string][]string // SUBSET 名 → 成员符号(按成员集合过滤;nil=普通符号) + subsets map[string][]string // SUBSET Name → Member Symbol (filter by member set; nil = common symbol) matchNumber int } -// placeholder 前缀:导航/聚合/符号限定字段求值后注入 base 的合成键。 +// placeholder prefix: Evaluates the navigation/aggregation/symbol limit field and injects the base composite key. const placeholderPrefix = "__cep_" -// ekind 标识表达式 token 的类别(仅 rewriter 内部用)。 +// ekind identifies the expression token category (used only internally in rewriter). type ekind int const ( @@ -57,8 +57,8 @@ type etoken struct { val string } -// tokenize 表达式为 token 流。仅服务于结构识别(函数调用、符号限定、括号配平); -// 算术/比较/逻辑的语义交给 expr 引擎,故运算符一律按原样保留。 +// The tokenize expression is token stream. It only serves structure recognition (function calls, symbol qualification, bracket balancing); +// The semantics of arithmetic/comparison/logic are handed over to the expr engine, so operators are always kept as is. func tokenize(s string) ([]etoken, error) { var toks []etoken runes := []rune(s) @@ -105,7 +105,7 @@ func tokenize(s string) ([]etoken, error) { toks = append(toks, etoken{ekNumber, string(runes[i:j])}) i = j default: - // 运算符(含多字符 >=, <=, !=, ==, &&, ||)一律收集为 op,原样保留。 + // Operators (with multicharacter >=, <=,!=, ==, &&, ||) All are collected as OP and kept as is. j := i for j < len(runes) && isOpRune(runes[j]) { j++ @@ -128,47 +128,47 @@ func isOpRune(c rune) bool { return false } -// navFuncs 是 CEP 导航/聚合函数名(rewriter 拦截它们,其余函数调用原样透传给 expr 引擎)。 +// navFuncs is the CEP navigation/aggregation function name (rewriter intercepts them, and other function calls are passed as-is to the expr engine). var navFuncs = map[string]bool{ "PREV": true, "NEXT": true, "FIRST": true, "LAST": true, "CLASSIFIER": true, "MATCH_NUMBER": true, "SUM": true, "AVG": true, "COUNT": true, "MIN": true, "MAX": true, } -// phKind 标识占位符的求值方式(预编译产物)。 +// phKind identification placeholder evaluation method (precompiled product). type phKind int const ( - phNav phKind = iota // 导航/聚合:name+args - phSym // 符号限定字段:name(symbol)+field + phNav phKind = iota // Navigation/aggregation: name+args + phSym // Symbol-limited field: name(symbol)+field ) -// phDesc 描述一个占位符如何从 matchCtx 求值。占位符键 = placeholderPrefix+i+"__"。 +// phDesc describes how a placeholder evaluates from matchCtx. Placeholder key = placeholderPrefix+i+ "__". type phDesc struct { kind phKind - name string // phNav: 函数名;phSym: 符号名 - args []string // phNav: 参数片段 - field string // phSym: 字段名(已去引号/反引号) - final bool // phNav: true=FINAL(整段匹配),false=RUNNING(到当前行,默认) + name string // phNav: Function name; phSym: symbol name + args []string // phNav: Parameter fragment + field string // phSym: Field name (unquoted/incorrect) + final bool // phNav: true=FINAL (full segment match), false=RUNNING (to current line, default) } -// preparedExpr 是预编译的 DEFINE/MEASURES 表达式:改写后的字符串经 expr.NewExpression -// 编译一次缓存;求值时只重算各占位符值与裸字段,复用编译产物(避免热路径重复解析)。 +// preparedExpr is a precompiled DEFINE/MEASURES expression: the rewritten string is processed by expr.NewExpression +// Compiling a single cache; When evaluating, only placeholder values and bare fields are recalculated, and the compiled product is reused (to avoid repeated parsing of hot paths). type preparedExpr struct { src string compiled *expr.Expression - phs []phDesc // 占位符描述(phs[i] → key placeholderPrefix+i+"__") - bareFields []string // 透传裸字段名(求值时灌入当前行字段) - needsHist bool // 含导航/聚合/符号限定 → 求值需历史行 + phs []phDesc // Placeholder Description (phs[i] → key placeholderPrefix+i+"__") + bareFields []string // Pass-through bare field name (inserted into the current row field during evaluation) + needsHist bool // Includes navigation/aggregation/symbol constraints → Evaluation requires historical rows } -// placeholderKey 返回第 i 个占位符的 base 键。 +// placeholderKey returns the base key of the i-th placeholder. func placeholderKey(i int) string { return placeholderPrefix + strconv.Itoa(i) + "__" } -// prepare 把表达式编译为 preparedExpr(tokenize → 改写计划 → NewExpression)。 -// 纯结构性,不求值;失败(词法/改写/编译)返回 error,供 Validate fail-fast。 +// prepare compiles the expression into preparedExpr(tokenize → rewrite plan → NewExpression). +// Purely structural, not valuable; Failure (lexical/paraphrase/compile) returns error for validate fail-fast. func prepare(src string, symbols map[string]bool) (*preparedExpr, error) { src = strings.TrimSpace(src) toks, err := tokenize(src) @@ -188,16 +188,16 @@ func prepare(src string, symbols map[string]bool) (*preparedExpr, error) { final := false if t.kind == ekIdent { up := strings.ToUpper(t.val) - // FINAL/RUNNING 前缀修饰紧随其后的导航/聚合(仅影响聚合与 FIRST/LAST)。 + // The FINAL/RUNNING prefix modifies immediately after navigation/aggregation (only affecting aggregation and FIRST/LAST). if (up == "FINAL" || up == "RUNNING") && i+2 < len(toks) && toks[i+1].kind == ekIdent && navFuncs[strings.ToUpper(toks[i+1].val)] && toks[i+2].kind == ekLParen { final = (up == "FINAL") - i++ // 消耗前缀,下移到函数名 token + i++ // Consume prefixes and move them down to the function token name t = toks[i] up = strings.ToUpper(t.val) } - // 函数调用:PREV(...) / SUM(...) 等。 + // Function calls: PREV(...) / SUM(...), etc. if navFuncs[up] && i+1 < len(toks) && toks[i+1].kind == ekLParen { args, consumed, err := readCallArgs(toks, i+1) if err != nil { @@ -208,8 +208,8 @@ func prepare(src string, symbols map[string]bool) (*preparedExpr, error) { i += 1 + consumed continue } - // SYMBOL.field(符号限定):SYMBOL 是已声明模式变量且后跟 .field。 - // field 可为标识符或反引号/引号包裹的保留字列名。 + // SYMBOL.field: SYMBOL is a declared mode variable followed by a.field. + // field can be an identifier or a reserved column name wrapped in backquotes/quotes. if symbols[t.val] && i+2 < len(toks) && toks[i+1].kind == ekDot && (toks[i+2].kind == ekIdent || toks[i+2].kind == ekString) { fld := fieldName(toks[i+2].val) @@ -219,7 +219,7 @@ func prepare(src string, symbols map[string]bool) (*preparedExpr, error) { continue } } - // 其余原样透传(裸字段、运算符、标量函数、字面量)。 + // The rest is passed as is (bare fields, operators, scalar functions, literals). pieces = append(pieces, t.val) if t.kind == ekIdent { p.bareFields = append(p.bareFields, t.val) @@ -228,7 +228,7 @@ func prepare(src string, symbols map[string]bool) (*preparedExpr, error) { } joined := joinTokens(pieces) if joined == "" { - return &preparedExpr{src: src, compiled: nil}, nil // 空表达式(未定义符号) + return &preparedExpr{src: src, compiled: nil}, nil // Null expression (undefined symbol) } compiled, err := expr.NewExpression(joined) if err != nil { @@ -238,16 +238,16 @@ func prepare(src string, symbols map[string]bool) (*preparedExpr, error) { return p, nil } -// evalPrepared 用 ctx 求值预编译表达式:重算占位符值 + 灌入裸字段,复用编译产物。 -// baseMapPool 复用 evalPrepared 的 base map:DEFINE/MEASURES 每次求值建一次 base(热路径主分配源)。 -// EvaluateValueWithNull 同步求值、返回标量/字段引用不持 base,Put 回 Pool 前清空即可复用。 +// evalPrepared evaluates the precompiled expression using ctx: recalculates placeholder values + injects bare fields, and reuses the compiled product. +// baseMapPool reuses evalPrepared's base map: DEFINE/MEASURES, and builds a base (main allocation source for hot paths) after each evaluation. +// EvaluateValueWithNull synchronously evaluates and returns scalar/field references without holding base; Put can be reused by clearing before returning to the pool. var baseMapPool = sync.Pool{ New: func() any { return make(map[string]any, 8) }, } func evalPrepared(p *preparedExpr, ctx *matchCtx) (any, bool, error) { if p == nil || p.compiled == nil { - return nil, true, nil // 空表达式 → NULL(调用方按未定义符号恒真处理) + return nil, true, nil // Null expressions: → NULL (caller is treated as inherent to undefined symbols) } base := baseMapPool.Get().(map[string]any) for k := range base { @@ -270,7 +270,7 @@ func evalPrepared(p *preparedExpr, ctx *matchCtx) (any, bool, error) { return v, isNull, err } -// evalDesc 求值一个占位符描述。 +// evalDesc evaluates a placeholder description. func evalDesc(d phDesc, ctx *matchCtx) any { switch d.kind { case phNav: @@ -292,8 +292,8 @@ func currentRow(ctx *matchCtx) map[string]any { return nil } -// readCallArgs 从 toks[at](应为 ekLParen)读取到配平的 ekRParen,返回括号内按顶层逗号 -// 切分的参数片段(每段是一组 token,原样拼接为字符串)与消耗的 token 数(含括号)。 +// readCallArgs reads the trimmed ekRParen from toks[at] (which should be ekLParen), returns the parentheses with a top comma +// The parameter fragments to be split (each segment is a set of tokens, concatenated as a string) and the number of tokens consumed (including parentheses). func readCallArgs(toks []etoken, at int) ([]string, int, error) { if at >= len(toks) || toks[at].kind != ekLParen { return nil, 0, fmt.Errorf("expected '(' after CEP function") @@ -333,7 +333,7 @@ func joinTokens(parts []string) string { var sb strings.Builder for i, p := range parts { if i > 0 { - // 标识符/数字与标识符/数字之间需要空格,避免粘成新 token;运算符与点号不加。 + // Spaces are needed between identifiers/numbers to avoid sticking them into new tokens; Operators and dots are not added. if needSpace(parts[i-1], p) { sb.WriteByte(' ') } @@ -355,8 +355,8 @@ func isAlphaNumLike(s string) bool { return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '_' } -// evalNav 求值一个导航/聚合调用。final 仅影响聚合与 FIRST/LAST(FINAL=整段匹配)。 -// args 为括号内顶层参数片段(已去括号、去外层逗号)。 +// evalNav evaluates a navigation/aggregation call. final only affects aggregation and FIRST/LAST (FINAL = whole segment match). +// args are the top-level parameter fragments inside parentheses (parentheses removed, outer commas removed). func evalNav(name string, args []string, ctx *matchCtx, final bool) (any, error) { switch name { case "CLASSIFIER": @@ -383,7 +383,7 @@ func evalNav(name string, args []string, ctx *matchCtx, final bool) (any, error) return nil, fmt.Errorf("unsupported CEP function %s", name) } -// posIndex 返回当前行下标:DEFINE=len(rows)(候选位置),MEASURES=cur。 +// posIndex returns the current row index: DEFINE=len(rows)(candidate position), MEASURES=cur. func posIndex(ctx *matchCtx) int { if ctx.candidate != nil { return len(ctx.rows) @@ -391,7 +391,7 @@ func posIndex(ctx *matchCtx) int { return ctx.cur } -// stripQuotes 去除首尾的反引号/单引号/双引号。 +// stripQuotes: Remove backquotes, single quotes, and double quotes from the beginning and end. func stripQuotes(a string) string { a = strings.TrimSpace(a) a = strings.Trim(a, "`") @@ -401,7 +401,7 @@ func stripQuotes(a string) string { return a } -// fieldName 从参数片段提取字段名(去 SYMBOL. 前缀、去引号/反引号)。COUNT(*) 的 '*' 原样返回。 +// fieldName Extracts field names from parameter fragments (remove SYMBOL. prefix, dequotation/backquotation). The '*' of COUNT(*) returns as is. func fieldName(arg string) string { a := stripQuotes(arg) if dot := strings.LastIndex(a, "."); dot >= 0 { @@ -410,8 +410,8 @@ func fieldName(arg string) string { return a } -// fieldAndSymbol 拆分聚合参数为(字段, 符号)。 -// "A.v" → ("v","A");"v" → ("v","");"*" → ("*","");反引号/引号包裹均去除。 +// fieldAndSymbol splits the aggregate parameter as (field, symbol). +// "A.v" → ("v","A"); "v" → ("v",""); "*" → ("*",""); Remove backquotes/quotation packets. func fieldAndSymbol(arg string) (field, symbol string) { a := strings.TrimSpace(arg) if dot := strings.IndexByte(a, '.'); dot >= 0 { @@ -431,8 +431,8 @@ func optInt(args []string, idx, def int) int { return def } -// rowsLabels 返回求值行集:DEFINE 含候选;MEASURES 下 final=false 截到当前行(RUNNING), -// final=true 取整段(FINAL)。aggregate 与 fromEndField 共用。 +// rowsLabels returns the evaluated row set: DEFINE with candidates; MEASURES final = false is taken to the current line (RUNNING), +// final = true takes the whole segment (FINAL). aggregate is shared with fromEndField. func rowsLabels(ctx *matchCtx, final bool) ([]map[string]any, []string) { rows := ctx.rows labels := ctx.labels @@ -451,7 +451,7 @@ func rowsLabels(ctx *matchCtx, final bool) ([]map[string]any, []string) { return rows, labels } -// positionalField 求 PREV/NEXT:从当前位置偏移取行。越界返回 nil。 +// positionalField to get PREV/NEXT: Offset from the current position to fetch rows. Crossing boundaries to return to nil. func positionalField(ctx *matchCtx, args []string, sign int) any { if len(args) == 0 { return nil @@ -465,8 +465,8 @@ func positionalField(ctx *matchCtx, args []string, sign int) any { return rows[idx][f] } -// fromEndField 求 FIRST/LAST:从头/尾取第 n 个(n 从 1 起)。n<1 钳为 1,避免越界。 -// final=true 时对整段匹配取(FINAL),否则截到当前行(RUNNING)。 +// fromEndField to find FIRST/LAST: take the nth from the end/end (n starts from 1). n<1 clamp is 1 to avoid crossing boundaries. +// When final=true, the entire segment is matched to FINAL; otherwise, it is cut to the current line (RUNNING). func fromEndField(ctx *matchCtx, args []string, fromHead bool, final bool) any { if len(args) == 0 { return nil @@ -494,10 +494,10 @@ func fromEndField(ctx *matchCtx, args []string, fromHead bool, final bool) any { return rows[idx][f] } -// aggregate 求匹配范围内的聚合(RUNNING 语义)。 -// 符号限定(SUM(A.x))仅对该符号标签行聚合;COUNT(expr) 计非 NULL 值,COUNT(*) 计行数。 -// 数值转换用 cast.ToFloat64E(支持 uint/字符串数字等),非数值按 NULL 跳过。 -// aggregate 求匹配范围内的聚合。final=true 取整段匹配(FINAL),否则截到当前行(RUNNING)。 +// aggregate to find the RUNNING semantics within the matching range. +// Symbol qualifiers (SUM(A.x)) aggregate only the row of the symbol label; COUNT(expr) counts non-NULL values, COUNT(*) counts the number of rows. +// Numerical conversion uses cast.ToFloat64E (supports uint/string numbers, etc.), skips non-numeric values by NULL. +// aggregate Calculate the aggregation within the matching range. final=true Matches the whole segment (FINAL); otherwise, it cuts to the current line (RUNNING). func aggregate(name string, args []string, ctx *matchCtx, final bool) any { var rows []map[string]any var labels []string @@ -512,7 +512,7 @@ func aggregate(name string, args []string, ctx *matchCtx, final bool) any { cntRows := 0 for i, r := range rows { if !labelMatches(labels[i], symbol, ctx.subsets) { - continue // 符号/SUBSET 限定:只计成分标签行 + continue // Symbol/SUBSET limit: Only the ingredient label line counts } cntRows++ if star { @@ -520,7 +520,7 @@ func aggregate(name string, args []string, ctx *matchCtx, final bool) any { } rv, has := r[f] if !has || rv == nil { - continue // NULL 不计 + continue // NULL ignores it } cntNonNull++ if x, err := cast.ToFloat64E(rv); err == nil { @@ -574,8 +574,8 @@ func aggregate(name string, args []string, ctx *matchCtx, final bool) any { return nil } -// labelMatches 报告 lbl 是否匹配 symbol(普通符号等值;symbol 是 SUBSET 名时属于其成分)。 -// 供 aggregate/resolveSymbolField/seqOfLabel 共用,保证 SUBSET 标签语义一致。 +// labelMatches reports whether lbl matches a symbol (a common symbol equivalent; symbol is a component of the SUBSET name). +// Shared by aggregate/resolveSymbolField/seqOfLabel to ensure consistency between SUBSET tags. func labelMatches(lbl, symbol string, subsets map[string][]string) bool { if symbol == "" || lbl == symbol { return true @@ -588,8 +588,8 @@ func labelMatches(lbl, symbol string, subsets map[string][]string) bool { return false } -// resolveSymbolField 取符号 SYMBOL(或 SUBSET 的任一成分)在匹配中最末出现行的字段值。 -// DEFINE 时若候选标签属于该符号/成分,取候选行该字段。 +// resolveSymbolField retrieves the last row of the SYMBOL (or any component of the SUBSET) in the match. +// When DEFINED, if the candidate tag belongs to that symbol/component, select the candidate line for that field. func resolveSymbolField(ctx *matchCtx, symbol, field string) any { if ctx.candidate != nil && labelMatches(ctx.candLabel, symbol, ctx.subsets) { return ctx.candidate[field] @@ -602,7 +602,7 @@ func resolveSymbolField(ctx *matchCtx, symbol, field string) any { return nil } -// truthy SQL 布尔语义:数值非零、布尔真、非空字符串为真。 +// truthy SQL Boolean semantics: Numeric values are nonzero, boolean true, and non-empty strings are true. func truthy(v any) bool { switch x := v.(type) { case bool: @@ -619,18 +619,18 @@ func truthy(v any) bool { return v != nil } -// EvalDefine 即时编译并求值符号的 DEFINE 条件(布尔)。仅供测试/外部使用; -// 引擎热路径用 Engine.evalDefine(预编译产物)。buffer=已匹配行,candidate=待分类行。 -// 不携带 SUBSET 成员表——含 SUBSET 限定的引用(如 S.v)需用 EvalDefineWithSubsets。 +// EvalDefine instantly compiles and evaluates the DEFINE condition (Boolean) of the symbol. For testing/external use only; +// The engine thermal path uses Engine.evalDefine (precompiled product). buffer = matched row, candidate = row to be classified. +// Does not carry SUBSET member tables—references with SUBSET-restricted elements (such as S.v) must be supported by EvalDefineWithSubsets. func EvalDefine(cond string, buffer []map[string]any, labels []string, candidate map[string]any, candLabel string, symbols map[string]bool) bool { return EvalDefineWithSubsets(cond, buffer, labels, candidate, candLabel, symbols, nil) } -// EvalDefineWithSubsets 同 EvalDefine,但携带 SUBSET 成员表,支持 SUBSET 限定的引用。 +// EvalDefineWithSubsets is the same as EvalDefine, but carries a SUBSET member table and supports SUBSET-specific references. func EvalDefineWithSubsets(cond string, buffer []map[string]any, labels []string, candidate map[string]any, candLabel string, symbols map[string]bool, subsets map[string][]string) bool { cond = strings.TrimSpace(cond) if cond == "" { - return true // 未定义的符号恒为真(SQL 标准) + return true // Undefined symbols are always true (SQL standard) } p, err := prepare(cond, symbols) if err != nil { @@ -644,13 +644,13 @@ func EvalDefineWithSubsets(cond string, buffer []map[string]any, labels []string return truthy(v) } -// EvalMeasure 即时编译并求值 MEASURES 表达式(值)。仅供测试/外部使用。 -// 不携带 SUBSET 成员表——含 SUBSET 限定的引用需用 EvalMeasureWithSubsets。 +// EvalMeasure instantly compiles and evaluates the MEASURES expression (value). For testing/external use only. +// Does not carry SUBSET member tables—references with SUBSET-specific references require EvalMeasureWithSubsets. func EvalMeasure(expression string, rows []map[string]any, labels []string, cur, matchNumber int, symbols map[string]bool) (any, bool) { return EvalMeasureWithSubsets(expression, rows, labels, cur, matchNumber, symbols, nil) } -// EvalMeasureWithSubsets 同 EvalMeasure,但携带 SUBSET 成员表,支持 SUBSET 限定的引用。 +// EvalMeasureWithSubsets is the same as EvalMeasure, but carries a SUBSET member table and supports references specific to SUBSET. func EvalMeasureWithSubsets(expression string, rows []map[string]any, labels []string, cur, matchNumber int, symbols map[string]bool, subsets map[string][]string) (any, bool) { p, err := prepare(expression, symbols) if err != nil { diff --git a/cep/eval_test.go b/cep/eval_test.go index 1f1f2aa..acfa5e4 100644 --- a/cep/eval_test.go +++ b/cep/eval_test.go @@ -23,14 +23,14 @@ func TestEvalDefine_BareComparison(t *testing.T) { } } -// 空条件(未定义符号)恒为真(SQL 标准)。 +// Null conditions (undefined symbols) are always true (SQL standard). func TestEvalDefine_EmptyIsTrue(t *testing.T) { if !EvalDefine("", nil, nil, map[string]any{"v": 1}, "A", syms("A")) { t.Errorf("empty DEFINE must be true") } } -// PREV:buffer 非空时取上一行的字段。 +// PREV:buffer Fetchs the field of the previous line when not null. func TestEvalDefine_Prev(t *testing.T) { buf := []map[string]any{{"v": 10.0}} labels := []string{"A"} @@ -44,21 +44,21 @@ func TestEvalDefine_Prev(t *testing.T) { } } -// PREV 越界返回 nil → 比较为假(首行无前驱)。 +// PREV crossing boundaries to return to nil → is relatively fake (no front-row drive). func TestEvalDefine_PrevNullEmptyBuffer(t *testing.T) { if EvalDefine("v > PREV(v, 1)", nil, nil, map[string]any{"v": 20.0}, "A", syms("A")) { t.Errorf("PREV nil → comparison should be false") } } -// 符号限定字段 A.v 等同候选行字段。 +// Symbol-qualified fields A.v are equivalent to candidate row fields. func TestEvalDefine_SymbolQualified(t *testing.T) { if !EvalDefine("A.v > 5", nil, nil, map[string]any{"v": 10.0}, "A", syms("A")) { t.Errorf("A.v=10 > 5 want true") } } -// 复合条件 AND + 字符串相等。 +// Compound condition: AND + strings are equal. func TestEvalDefine_AndStringEq(t *testing.T) { cond := "v > 5 AND type == \"x\"" if !EvalDefine(cond, nil, nil, map[string]any{"v": 10.0, "type": "x"}, "A", syms("A")) { @@ -69,7 +69,7 @@ func TestEvalDefine_AndStringEq(t *testing.T) { } } -// --- EvalMeasure(candidate=nil,MEASURES 路径)--- +// --- EvalMeasure(candidate=nil, MEASURES path)--- func TestEvalMeasure_BareField(t *testing.T) { rows := []map[string]any{{"v": 10.0}, {"v": 20.0}} @@ -105,7 +105,7 @@ func TestEvalMeasure_ClassifierAndMatchNumber(t *testing.T) { } } -// 聚合:RUNNING(到当前行 cur=2 即全部 3 行)。 +// Aggregate: RUNNING (cur=2 to the current row, i.e., all 3 rows). func TestEvalMeasure_Aggregates(t *testing.T) { rows := []map[string]any{{"v": 10.0}, {"v": 20.0}, {"v": 30.0}} labels := []string{"A", "A", "A"} @@ -124,17 +124,17 @@ func TestEvalMeasure_Aggregates(t *testing.T) { check("MAX(v)", 30) } -// 聚合 RUNNING:cur=1 时只算前两行。 +// When aggregated RUNNING:cur=1, only the first two rows are counted. func TestEvalMeasure_AggregateRunning(t *testing.T) { rows := []map[string]any{{"v": 10.0}, {"v": 20.0}, {"v": 30.0}} labels := []string{"A", "A", "A"} - v, _ := EvalMeasure("SUM(v)", rows, labels, 1, 1, syms("A")) // cur=1 → 前 2 行 + v, _ := EvalMeasure("SUM(v)", rows, labels, 1, 1, syms("A")) // cur=1 → the first 2 rows if asFloat(v) != 30.0 { t.Errorf("RUNNING SUM at cur=1 want 30, got %v", v) } } -// 算术组合:MAX - MIN。 +// Arithmetic combination: MAX - MIN. func TestEvalMeasure_Arithmetic(t *testing.T) { rows := []map[string]any{{"v": 10.0}, {"v": 5.0}, {"v": 30.0}} labels := []string{"A", "A", "A"} @@ -144,7 +144,7 @@ func TestEvalMeasure_Arithmetic(t *testing.T) { } } -// 符号限定字段:取该符号最末出现行。 +// Symbol Limit Field: Take the last line of the symbol. func TestEvalMeasure_SymbolField(t *testing.T) { rows := []map[string]any{{"v": 10.0}, {"v": 20.0}} labels := []string{"A", "A"} @@ -154,8 +154,8 @@ func TestEvalMeasure_SymbolField(t *testing.T) { } } -// FIRST/LAST 的 RUNNING 语义:ALL ROWS PER MATCH 下随当前行推进(与 COUNT 一致)。 -// LAST(v) at cur=0/1/2 → 10/20/30(而非恒为末行 30 的 FINAL)。 +// RUNNING semantics of FIRST/LAST: ALL ROWS PER MATCH advance with current row (consistent with COUNT). +// LAST(v) at cur=0/1/2 → 10/20/30 (rather than FINAL at the end row 30). func TestEvalMeasure_FirstLastRunning(t *testing.T) { rows := []map[string]any{{"v": 10.0}, {"v": 20.0}, {"v": 30.0}} labels := []string{"A", "A", "A"} @@ -169,13 +169,13 @@ func TestEvalMeasure_FirstLastRunning(t *testing.T) { if v, _ := EvalMeasure("LAST(v)", rows, labels, 2, 1, sym); asFloat(v) != 30.0 { t.Errorf("LAST at cur=2 want 30, got %v", v) } - // FIRST 恒为首行(RUNNING 与 FINAL 一致)。 + // FIRST always takes the first line (RUNNING and FINAL are the same). if v, _ := EvalMeasure("FIRST(v)", rows, labels, 1, 1, sym); asFloat(v) != 10.0 { t.Errorf("FIRST at cur=1 want 10, got %v", v) } } -// FIRST/LAST 的 n<=0 不应越界 panic,钳为 n=1。 +// The n<=0 of FIRST/LAST should not be crossed by panic; the clamp should be n=1. func TestEvalMeasure_FirstLastZeroN(t *testing.T) { rows := []map[string]any{{"v": 10.0}, {"v": 20.0}} labels := []string{"A", "A"} @@ -191,12 +191,12 @@ func TestEvalMeasure_FirstLastZeroN(t *testing.T) { } } -// 符号限定聚合 SUM(A.v) 仅对该符号标签行求和(非全部行)。 +// Symbol-limited aggregation SUM(A.v) sums only the row of the symbol label (not all rows). func TestEvalMeasure_AggregateSymbolScoped(t *testing.T) { rows := []map[string]any{{"v": 1.0}, {"v": 2.0}, {"v": 3.0}} labels := []string{"A", "B", "A"} sym := syms("A", "B") - // SUM(A.v)=1+3=4(只 A 行);SUM(v)=1+2+3=6(全部行)。 + // SUM(A.v) = 1 + 3 = 4 (only row A); SUM(v) = 1 + 2 + 3 = 6 (all rows). if v, _ := EvalMeasure("SUM(A.v)", rows, labels, 2, 1, sym); asFloat(v) != 4.0 { t.Errorf("SUM(A.v) want 4 (A-scoped), got %v", v) } @@ -205,7 +205,7 @@ func TestEvalMeasure_AggregateSymbolScoped(t *testing.T) { } } -// COUNT(expr) 计非 NULL 值(含字符串/uint),非仅数值。 +// COUNT(expr) Counts non-NULL values (including string/uint), not just numeric values. func TestEvalMeasure_CountNonNull(t *testing.T) { rows := []map[string]any{{"name": "a"}, {"name": "b"}, {"name": "c"}} labels := []string{"A", "A", "A"} @@ -213,7 +213,7 @@ func TestEvalMeasure_CountNonNull(t *testing.T) { if v, _ := EvalMeasure("COUNT(name)", rows, labels, 2, 1, sym); asFloat(v) != 3.0 { t.Errorf("COUNT(name) want 3 (non-NULL strings), got %v", v) } - // uint 列也能被 SUM 聚合(cast.ToFloat64E 支持)。 + // uint columns can also be aggregated by SUM (cast.ToFloat64E supported). urows := []map[string]any{{"n": uint64(2)}, {"n": uint64(3)}} ulabels := []string{"A", "A"} if v, _ := EvalMeasure("SUM(n)", urows, ulabels, 1, 1, sym); asFloat(v) != 5.0 { @@ -231,7 +231,7 @@ func TestTokenize_Simple(t *testing.T) { if len(toks) == 0 { t.Fatalf("expected tokens") } - // 首个 token 是标识符 v + // The first token is the identifier v if toks[0].kind != ekIdent || toks[0].val != "v" { t.Errorf("first token=%+v want ident v", toks[0]) } @@ -243,9 +243,9 @@ func TestTokenize_UnterminatedQuote(t *testing.T) { } } -// --- SUBSET(符号按成员集合过滤)--- +// --- SUBSET (filter symbols by member set) --- -// SUM(S.v) 对 SUBSET 全部成分行求和;COUNT(S.v) 计成分非 NULL 行。 +// SUM(S.v) sums all components of the SUBSET; COUNT(S.v) counts the non-NULL row. func TestAggregate_SubsetScoped(t *testing.T) { ctx := &matchCtx{ rows: []map[string]any{{"v": 1.0}, {"v": 2.0}, {"v": 3.0}}, @@ -262,24 +262,24 @@ func TestAggregate_SubsetScoped(t *testing.T) { if v := aggregate("MAX", []string{"S.v"}, ctx, false); asFloat(v) != 3.0 { t.Errorf("MAX(S.v) want 3, got %v", v) } - // SUM(A.v) 仍只计 A 行(普通符号路径不受影响)。 + // SUM(A.v) still counts only line A (the normal symbol path is not affected). if v := aggregate("SUM", []string{"A.v"}, ctx, false); asFloat(v) != 4.0 { t.Errorf("SUM(A.v) want 4 (1+3), got %v", v) } } -// resolveSymbolField:SUBSET 取成分最末出现行;DEFINE 候选标签属成分时取候选行。 +// resolveSymbolField: SUBSET takes the last row of components; DEFINE candidate labels take candidate rows when they belong to the category. func TestResolveSymbolField_Subset(t *testing.T) { ctx := &matchCtx{ rows: []map[string]any{{"v": 1.0}, {"v": 2.0}, {"v": 3.0}}, labels: []string{"A", "B", "A"}, subsets: map[string][]string{"S": {"A", "B"}}, } - // 倒序首个属 S 的行:idx2=A → v=3。 + // The first row in reverse order belonging to S: idx2 = A → v = 3. if v := resolveSymbolField(ctx, "S", "v"); asFloat(v) != 3.0 { t.Errorf("S.v want 3 (last S member), got %v", v) } - // 候选为 B(属 S)→ 取候选行。 + // Candidate is B (S classification→ Choose candidate row. cand := &matchCtx{ rows: []map[string]any{{"v": 1.0}}, labels: []string{"A"}, @@ -292,21 +292,21 @@ func TestResolveSymbolField_Subset(t *testing.T) { } } -// --- FINAL 语义(ALL ROWS 下 FINAL 取整段匹配,RUNNING 截到当前行)--- +// --- FINAL semantics (FINAL matches the whole segment under ALL ROWS, RUNNING cuts to the current row)--- -// FINAL SUM/LAST 取整段匹配的行集(不受 cur 截断);RUNNING 截到当前行。 +// FINAL SUM/LAST FETCH THE ROW SET MATCHING segments (not cur truncation); RUNNING: Capture the current line. func TestEvalMeasure_AggregateFinal(t *testing.T) { rows := []map[string]any{{"v": 10.0}, {"v": 20.0}, {"v": 30.0}} labels := []string{"A", "A", "A"} sym := syms("A") - // FINAL SUM(v) 在 cur=1 时仍取全部 3 行 = 60;RUNNING SUM(v) 取前 2 行 = 30。 + // FINAL SUM(v) takes all 3 rows = 60 when cur=1; RUNNING SUM(v) takes the first 2 rows = 30. if v, _ := EvalMeasure("FINAL SUM(v)", rows, labels, 1, 1, sym); asFloat(v) != 60.0 { t.Errorf("FINAL SUM at cur=1 want 60, got %v", v) } if v, _ := EvalMeasure("RUNNING SUM(v)", rows, labels, 1, 1, sym); asFloat(v) != 30.0 { t.Errorf("RUNNING SUM at cur=1 want 30, got %v", v) } - // FINAL LAST(v) = 末行 30(与 cur 无关);RUNNING LAST(v) at cur=1 = 20。 + // FINAL LAST(v) = last line 30 (unrelated to cur); RUNNING LAST(v) at cur=1 = 20. if v, _ := EvalMeasure("FINAL LAST(v)", rows, labels, 1, 1, sym); asFloat(v) != 30.0 { t.Errorf("FINAL LAST want 30, got %v", v) } @@ -315,7 +315,7 @@ func TestEvalMeasure_AggregateFinal(t *testing.T) { } } -// 无前缀默认 RUNNING(向后兼容);FINAL FIRST 恒为首行(与 RUNNING 一致)。 +// No prefix defaults to RUNNING (backward compatible); FINAL FIRST always starts with the first line (consistent with RUNNING). func TestEvalMeasure_FinalDefault(t *testing.T) { rows := []map[string]any{{"v": 10.0}, {"v": 20.0}, {"v": 30.0}} labels := []string{"A", "A", "A"} @@ -328,7 +328,7 @@ func TestEvalMeasure_FinalDefault(t *testing.T) { } } -// EvalMeasureWithSubsets 正确求 SUBSET 限定聚合;旧 EvalMeasure(nil subsets)返回 0。 +// EvalMeasureWithSubsets correctly determines the SUBSET constraint aggregation; Old EvalMeasure (nil subsets) returns 0. func TestEvalMeasure_SubsetViaWithSubsets(t *testing.T) { rows := []map[string]any{{"v": 1.0}, {"v": 2.0}, {"v": 3.0}} labels := []string{"A", "B", "A"} @@ -339,6 +339,6 @@ func TestEvalMeasure_SubsetViaWithSubsets(t *testing.T) { } v0, _ := EvalMeasure("SUM(S.v)", rows, labels, 2, 1, syms("A", "B", "S")) if asFloat(v0) != 0 { - t.Errorf("EvalMeasure(no subsets) SUM(S.v) want 0 (不支持), got %v", v0) + t.Errorf("EvalMeasure(no subsets) SUM(S.v) want 0 (not supported), got %v", v0) } } diff --git a/cep/nfa.go b/cep/nfa.go index 5180318..530a488 100644 --- a/cep/nfa.go +++ b/cep/nfa.go @@ -1,29 +1,29 @@ package cep -// stateKind 标识 NFA 状态类别。 +// stateKind identifies the NFA state category. type stateKind int const ( - stEpsilon stateKind = iota // 不消费行,沿 out1/out2 前进 - stMatch // 消费一行:当行满足 symbol 的 DEFINE 时,沿 out1 前进 - stAccept // 接受态:一次完整匹配 + stEpsilon stateKind = iota // Don't spend money, just move forward along out1/out2 + stMatch // Consuming a line: When a line satisfies the DEFINE of the symbol, it moves forward along out1 + stAccept // Acceptance state: a complete match at once ) -// state 是 NFA 的一个状态。DEFINE 条件按 symbol 在引擎处查表,故 state 只携 symbol。 +// state is a state of the NFA. The DEFINE condition is checked in the engine by the symbol, so state only carries the symbol. type state struct { kind stateKind - symbol string // stMatch:模式变量名 + symbol string // stMatch: Name of the mode variable out1 *state - out2 *state // 仅 stEpsilon 的选择/循环用到 + out2 *state // Only stEpsilon is selected/recycled } -// NFA 是编译后的模式自动机:start 经 epsilon/match 转移到达 accept。 +// NFA is a compiled mode automaton: start is transferred via epsilon/match to accept. type NFA struct { start *state accept *state } -// frag 是 Thompson 构造的片段:start 与若干待接续的出边(dots 指向 nil 的 out 槽)。 +// frag is a piece constructed by Thompson: start and several out-of-the-line edges to be joined (dots point to the out slot of nil). type frag struct { start *state dots []**state @@ -55,21 +55,21 @@ func alt(a, b *frag) *frag { return &frag{start: s, dots: append(a.dots, b.dots...)} } -// starFrag 构造 child*(贪婪:优先回环)。 +// starFrag constructs child* (greedy: priority loop). func starFrag(child *frag) *frag { s := &state{kind: stEpsilon} - patch(child, s) // 子片段结束后回到分支点 + patch(child, s) // After the subsegment ends, return to the branching point s.out1 = child.start - return &frag{start: s, dots: []**state{&s.out2}} // out2=出口(待接续) + return &frag{start: s, dots: []**state{&s.out2}} // out2=Exit (to be continued) } -// optFrag 构造 child?(0 或 1)。 +// optFrag constructs child?(0 or 1). func optFrag(child *frag) *frag { s := &state{kind: stEpsilon, out1: child.start} return &frag{start: s, dots: append(child.dots, &s.out2)} } -// closure 计算 starts 经 epsilon 转移可达的全部状态(含 match/accept 终态)。 +// closure calculates all states that can be reached via epsilon transfer (including match/accept final states). func closure(starts ...*state) []*state { seen := make(map[*state]bool, len(starts)) var stack []*state @@ -97,7 +97,7 @@ func closure(starts ...*state) []*state { return out } -// hasAccept 报告状态集是否含接受态。 +// hasAccept reports whether the state set contains the acceptance state. func hasAccept(states []*state) bool { for _, s := range states { if s.kind == stAccept { @@ -107,13 +107,13 @@ func hasAccept(states []*state) bool { return false } -// isComplete 报告状态集是否「到达接受态且无法再延伸」(贪婪终结)。 -// 含 accept 但仍有 match-state 的(如 A* 续配)不算终结,应继续贪婪延伸。 +// isComplete reports whether the state set has 'reached the accepting state and can no longer be extended' (Greed Ends). +// Items containing accept but still having match-state (such as A* continuation) do not end and should continue to be greedily extended. func isComplete(states []*state) bool { return hasAccept(states) && len(matchStates(states)) == 0 } -// matchStates 抽出状态集中的全部 stMatch(待消费行测试)。 +// matchStates extracts all stMatch (pending line testing) from the state set. func matchStates(states []*state) []*state { var ms []*state for _, s := range states { diff --git a/cep/nfa_test.go b/cep/nfa_test.go index 7e86e61..5cffabf 100644 --- a/cep/nfa_test.go +++ b/cep/nfa_test.go @@ -4,7 +4,7 @@ import ( "testing" ) -// 构造一个最小 NFA 片段用于低层测试:start --epsilon--> m(A) --out--> accept +// Construct a minimal NFA fragment for low-level testing: start --epsilon--> m(A) --out--> accept func buildLinearA() (start, accept *state, matchA *state) { matchA = &state{kind: stMatch, symbol: "A"} accept = &state{kind: stAccept} @@ -13,8 +13,8 @@ func buildLinearA() (start, accept *state, matchA *state) { return } -// closure 经 epsilon 转移应含起点与可达的 match 终态,但**不穿越 match 态**(accept -// 在 match 态之后,需消费一行后才可达,故不在 start 的 epsilon 闭包里)。 +// closure Transition via epsilon should include the start and the reachable match final state, but **does not cross the match state** (accept +// After the match state, one line must be consumed before reaching it, so it is not included in the start epsilon closure). func TestClosure_Epsilon(t *testing.T) { start, accept, matchA := buildLinearA() got := closure(start) @@ -29,20 +29,20 @@ func TestClosure_Epsilon(t *testing.T) { } } -// closure 遇 stMatch 终止沿该分支前进(match 不消费,故不跟出其 out)。 +// closure When stMatch terminates, it proceeds along that branch (match does not consume, so it does not follow its out). func TestClosure_StopsAtMatch(t *testing.T) { _, _, matchA := buildLinearA() - // 从 matchA 出发:match 态本身在闭包里,但其 out(accept)不应被 epsilon 闭包带入。 + // Starting from matchA: The match state itself is in the closure, but its out (accept) should not be brought in by the epsilon closure. got := closure(matchA) if containsState(got, &state{kind: stAccept}) { - // 上面的 &state{} 是新建实例,不会在 got 里;仅用于触发 containsState 语义。 + // The above '%state{} is a new instance and won't be inside 'got'; Only used to trigger the containsState semantics. } if len(got) != 1 { t.Errorf("closure of a lone match-state want size 1, got %d", len(got)) } } -// 选择分支:epsilon 分裂到两条 match,closure 应含两者。 +// Select branch: Split epsilon into two matches, closure should include both. func TestClosure_AltSplit(t *testing.T) { a := &state{kind: stMatch, symbol: "A"} b := &state{kind: stMatch, symbol: "B"} @@ -53,7 +53,7 @@ func TestClosure_AltSplit(t *testing.T) { } } -// isComplete:含 accept 且无 match-state → true;含 accept 且有 match-state → false。 +// isComplete: contains accept and no match-state → true; Contains accept and has match-state → false. func TestIsComplete(t *testing.T) { accept := &state{kind: stAccept} if !isComplete([]*state{accept}) { @@ -68,7 +68,7 @@ func TestIsComplete(t *testing.T) { } } -// hasAccept / matchStates。 +// hasAccept / matchStates. func TestHasAcceptAndMatchStates(t *testing.T) { accept := &state{kind: stAccept} matchA := &state{kind: stMatch, symbol: "A"} @@ -84,7 +84,7 @@ func TestHasAcceptAndMatchStates(t *testing.T) { } } -// patch 把片段的待接续出边指向目标。 +// Patch points the pending edge of the clip to the target. func TestPatch(t *testing.T) { s := &state{kind: stMatch, symbol: "A"} f := &frag{start: s, dots: []**state{&s.out1}} diff --git a/cep/pattern.go b/cep/pattern.go index 696cbcd..c344a5c 100644 --- a/cep/pattern.go +++ b/cep/pattern.go @@ -6,8 +6,8 @@ import ( "github.com/rulego/streamsql/types" ) -// Compile 把模式树编译为 NFA(Thompson 构造)。组合式节点:序列/选择/分组/PERMUTE/量词。 -// PatternExclusion({- -},absence)暂不支持,返回明确错误。 +// Compile compiles the pattern tree into an NFA (Thompson construct). Composable nodes: sequence/selection/grouping/PERMUTE/quantifier. +// PatternExclusion({- -}, absence) is not supported at this time and returns a definite error. func Compile(node *types.PatternNode) (*NFA, error) { if node == nil { return nil, fmt.Errorf("MATCH_RECOGNIZE requires a PATTERN") @@ -46,7 +46,7 @@ func compileNode(n *types.PatternNode) (*frag, error) { } return f, nil case types.PatternGroup: - // Group 透明:编译其内部序列。量词由外层 Repetition 处理。 + // Group transparency: compiles its internal sequence. Quantifiers are processed by the outer Repetition. if len(n.Children) == 0 { return newEpsFrag(), nil } @@ -81,8 +81,8 @@ func compileNode(n *types.PatternNode) (*frag, error) { return nil, fmt.Errorf("unknown pattern node kind %d", n.Kind) } -// compileRepeat 展开量词为显式 NFA:{n}=n 份;{n,}=n 份 + 星;{n,m}=n 份 + (m-n) 份可选; -// 每份重新编译子节点(独立状态),避免回环打结。 +// compileRepeat expands the quantifier to explicit NFA: {n} = n parts; {n,} = n parts + stars; {n,m} = n parts + (m-n) parts optional; +// Each copy is recompiled to recompile child nodes (independent state) to avoid looping knots. func compileRepeat(child *types.PatternNode, q *types.Quantifier) (*frag, error) { if q.Min < 0 { return nil, fmt.Errorf("quantifier min must be >= 0") @@ -126,23 +126,23 @@ func compileRepeat(child *types.PatternNode, q *types.Quantifier) (*frag, error) } } if f == nil { - return newEpsFrag(), nil // {0}:匹配空 + return newEpsFrag(), nil // {0}: Match empty } return f, nil } -// compilePermute 把 PERMUTE(A,B,...) 编译为所有排列的交替(任一顺序匹配)。 +// compilePermute compiles PERMUTE(A, B,...) into all permutations (any order match). func compilePermute(children []*types.PatternNode) (*frag, error) { if len(children) == 0 { return newEpsFrag(), nil } - // 排列数为 N!,符号过多会导致 NFA 状态阶乘级膨胀,设上限保护。 + // If the number of permutations is N!, too many symbols will cause the NFA state to expand exponentially, so limit protection is set. if len(children) > 6 { return nil, fmt.Errorf("PERMUTE supports at most 6 symbols (got %d): factorial state blow-up", len(children)) } var result *frag for _, perm := range permutations(len(children)) { - // 每个排列独立状态:按索引顺序重新编译子节点。 + // Each permutation has its own independent state: Recompile child nodes in index order. var f *frag for _, idx := range perm { cf, err := compileNode(children[idx]) @@ -164,7 +164,7 @@ func compilePermute(children []*types.PatternNode) (*frag, error) { return result, nil } -// permutations 返回 [0,n) 的所有排列索引。 +// permutations returns all permutation indexes for [0,n). func permutations(n int) [][]int { if n == 0 { return [][]int{{}} diff --git a/cep/pattern_test.go b/cep/pattern_test.go index 997713c..74a1662 100644 --- a/cep/pattern_test.go +++ b/cep/pattern_test.go @@ -6,7 +6,7 @@ import ( "github.com/rulego/streamsql/types" ) -// Compile 合法模式:返回非空 NFA(start/accept 非 nil)且无错误。 +// Compile valid mode: Returns non-null NFA (start/accept not nil) without errors. func TestCompile_ValidPatterns(t *testing.T) { cases := []struct { name string @@ -38,7 +38,7 @@ func TestCompile_ValidPatterns(t *testing.T) { } } -// Compile 非法模式:明确报错。 +// Compile Illegal Mode: Explicitly reports errors. func TestCompile_InvalidPatterns(t *testing.T) { cases := []struct { name string @@ -62,7 +62,7 @@ func TestCompile_InvalidPatterns(t *testing.T) { } } -// {0} 量词匹配空:应编译成功(空片段),NFA 仍可达 accept。 +// {0} Quantifier Match Null: The null segment should be compiled successfully, and the NFA can still reach accept. func TestCompile_ZeroQuantifier(t *testing.T) { nfa, err := Compile(rep(lit("A"), 0, 0)) if err != nil { @@ -73,7 +73,7 @@ func TestCompile_ZeroQuantifier(t *testing.T) { } } -// PERMUTE 多符号:编译成功(排列数为 N!,符号多时状态膨胀,这里仅验证 3 符号可编译)。 +// PERMUTE multi-symbol: compilation successful (permutation N!, states expand when there are many symbols; here only 3 symbols are verified). func TestCompile_PermuteThree(t *testing.T) { node := &types.PatternNode{Kind: types.PatternPermute, Children: []*types.PatternNode{ lit("A"), lit("B"), lit("C"), @@ -83,7 +83,7 @@ func TestCompile_PermuteThree(t *testing.T) { } } -// PERMUTE 超过 6 符号:阶乘级状态膨胀,编译期拒绝。 +// PERMUTE exceeds 6 symbols: factorial-level state expansion, compile time rejection. func TestCompile_PermuteLimit(t *testing.T) { kids := make([]*types.PatternNode, 7) for i := range kids { diff --git a/cep/stress_test.go b/cep/stress_test.go index 4f08004..2c17fc0 100644 --- a/cep/stress_test.go +++ b/cep/stress_test.go @@ -9,9 +9,9 @@ import ( "github.com/rulego/streamsql/types" ) -// CEP 压力测试(引擎核心层,同步 Process):直接调 engine.Process 绕过 streamsql async 通道, -// 精确控制时序与堆采样。WITHIN sweeper 主动过期 + 吞吐 benchmark。 -// 本地普通模式跑(-race 由 CI/Linux 回归)。 +// CEP stress testing (engine core layer, synchronous process): directly tune the engine.Process bypasses the streamsql async channel, +// Precise timing control and heap sampling. WITHIN sweeper active expiration + throughput benchmark. +// Local normal mode running (-race reverts from CI/Linux). func readHeap() uint64 { runtime.GC() @@ -20,7 +20,7 @@ func readHeap() uint64 { return ms.HeapAlloc } -// countRuns 统计所有分区 p.runs 里的活跃部分匹配数(sweep 清理的对象)。同包可访问私有字段。 +// countRuns counts the number of matches for active parts in all partitions (p.runs (objects cleaned by sweep). The same packet can access private fields. func (e *Engine) countRuns() int { e.mu.Lock() defer e.mu.Unlock() @@ -31,16 +31,16 @@ func (e *Engine) countRuns() int { return n } -// --- WITHIN sweeper 主动过期 --- +// --- WITHIN sweeper Actively expired --- -// 空闲分区的超窗部分匹配必须被 sweeper 主动清(不能只靠下一事件被动清)。 -// 多分区每分区灌 1 个 A(A 等 B 死等,run 是未 complete 的中间态,驻留 p.runs 不进 pending); -// ts 用 epoch(UnixMilli)使 sweep 的 wall-clock 判定生效。Sleep > WITHIN + 多个 sweep 周期后, -// 直接读 p.runs 计数断言 sweeper 已清空(精确,不依赖堆噪声)。 -// 若 sweeper 失效:空闲无新事件触发被动清,run 驻留,runsAfter == runsBefore。 +// The superwindow part of the free partition must be actively cleared by the sweeper (not passively cleared only from the next event). +// Multi-partition fed one A per partition (A equals B dead, run is an incomplete intermediate state, residing p.runs does not enter pending); +// ts uses epoch (UnixMilli) to make the wall-clock check for sweep valid. After Sleep > WITHIN + multiple sweep cycles, +// Directly reading p.runs counts asserts that the sweeper has been emptied (precise, independent of heap noise). +// If sweeper fails: Idle with no new events triggers passive clearing, run retention, runsAfter == runsBefore. func TestStressCEP_WithinSweeper_ActiveExpiry(t *testing.T) { - // B 恒假(v<0,灌的 v=60 永不满足):run 匹配 A 后死等 B,进「等B」中间态(无 accept), - // 故走 survivors → p.runs,不进 completions/pending(pending 不由 sweep 清)。 + // B is inherently false (v<0, the fed v=60 is never satisfied): run Wait for B after matching A, enter the intermediate state of 'waiting for B' (no accept), + // Therefore, follow survivors → p.runs, not completions/pending (pending is not cleared by sweep). spec := &types.MatchRecognizeSpec{ Pattern: seq(lit("A"), lit("B")), Defines: []types.MatchDefine{def("A", "v > 50"), def("B", "v < 0")}, @@ -55,8 +55,8 @@ func TestStressCEP_WithinSweeper_ActiveExpiry(t *testing.T) { e.Start() defer e.Stop() - // 每分区灌 1 个 A:seq(A,B) 下后续 A 会杀前一个等 B 的 run,故每分区仅留 1 run 驻留。 - // 用多分区堆出大量驻留 run(每 run 是独立的等 B 中间态)。 + // Install one A per partition: seq(A,B) will kill the previous run waiting for B, so only one run resides in each partition. + // Use multi-partition stacking to produce a large number of dwell runs (each run is an independent intermediate state equal to B). const partitions = 3000 ts := time.Now().UnixMilli() for p := 0; p < partitions; p++ { @@ -66,25 +66,25 @@ func TestStressCEP_WithinSweeper_ActiveExpiry(t *testing.T) { runsBefore := e.countRuns() heap1 := readHeap() if runsBefore == 0 { - t.Fatal("前置失败:灌 A 后无驻留 run,无法验证 sweeper") + t.Fatal("Pre-installation failure: No resident run after A installation, unable to verify sweeper") } - // Sleep 远超 WITHIN(200ms)且覆盖多个 sweep 周期(sweepInterval≈100ms), - // 让 sweeper 主动扫并清空闲分区的超窗 run。 + // Sleep far exceeds WITHIN (200ms) and covers multiple sweep cycles (sweepInterval ≈100ms), + // Allows the sweeper to actively sweep and clear the overwindow run of the free partition. time.Sleep(700 * time.Millisecond) runsAfter := e.countRuns() heap2 := readHeap() - t.Logf("sweeper 主动过期: p.runs %d → %d;heap %.2fMB → %.2fMB", + t.Logf("sweeper Actively expired: p.runs %d → %d; heap %.2fMB → %.2fMB", runsBefore, runsAfter, float64(heap1)/1e6, float64(heap2)/1e6) - // sweeper 生效:超窗 run 被主动清空(runsAfter 应为 0)。 + // sweeper activates: the overwindow run is actively cleared (runsAfter should be 0). if runsAfter != 0 { - t.Fatalf("sweeper 未主动清空闲分区超窗 run:p.runs %d → %d(应清空)", runsBefore, runsAfter) + t.Fatalf("sweeper Failure to proactively clear idle partitions and overrun windows run:p.runs %d → %d (should be cleared)", runsBefore, runsAfter) } } -// --- 吞吐 benchmark(同步 Process,测引擎真实吞吐,不受 async 通道影响)--- +// --- Throughput benchmark (synchronized process, tests the engine's true throughput, unaffected by async channels)--- func benchCep(b *testing.B, spec *types.MatchRecognizeSpec, key func(i int) string, row func(i int) map[string]any) { b.Helper() @@ -100,7 +100,7 @@ func benchCep(b *testing.B, spec *types.MatchRecognizeSpec, key func(i int) stri } } -// 顺序模式 A B(最常见 CEP 路径)。 +// Sequential pattern A B (the most common CEP path). func BenchmarkCEP_Sequence(b *testing.B) { benchCep(b, &types.MatchRecognizeSpec{ Pattern: seq(lit("A"), lit("B")), @@ -117,7 +117,7 @@ func BenchmarkCEP_Sequence(b *testing.B) { }) } -// 星号贪婪 A* B(测 greedy/pending 选最长路径)。 +// Asterisk: Greedy A* B (for greedy/pending, choose the longest path). func BenchmarkCEP_Star(b *testing.B) { benchCep(b, &types.MatchRecognizeSpec{ Pattern: seq(rep(lit("A"), 0, -1), lit("B")), @@ -134,18 +134,18 @@ func BenchmarkCEP_Star(b *testing.B) { }) } -// 分区顺序模式(PARTITION BY + 分区键计算开销)。 +// Partition order mode (PARTITION BY + partition key calculates overhead). func BenchmarkCEP_Partitioned(b *testing.B) { benchCep(b, &types.MatchRecognizeSpec{ - Pattern: seq(lit("A"), lit("B")), - Defines: []types.MatchDefine{def("A", "v > 50"), def("B", "v < 50")}, - OrderBy: orderBy("ts"), - Measures: []types.Measure{measure("MATCH_NUMBER()", "mn")}, + Pattern: seq(lit("A"), lit("B")), + Defines: []types.MatchDefine{def("A", "v > 50"), def("B", "v < 50")}, + OrderBy: orderBy("ts"), + Measures: []types.Measure{measure("MATCH_NUMBER()", "mn")}, PartitionBy: []string{"deviceId"}, }, func(i int) string { return "dev-" + strconv.Itoa(i%4) }, func(i int) map[string]any { - // v 按 (i/4)%2 交替:同分区事件 i, i+4, i+8… 的 v 才 A B 交替(若按 i%2, - // dev-0/2 区只来 A、dev-1/3 区只来 B → 零匹配 emit,测的是种子+死而非密集匹配)。 + // v alternates by (i/4)%2: i, i+4, i+8 in the same partition... v alternates between A and B (if i%2, + // dev-0/2 only comes to A, dev-1/3 only B→ zero-match emit, tested for seed + death rather than dense matching). v := 60.0 if (i/4)%2 == 1 { v = 40.0 diff --git a/condition/condition.go b/condition/condition.go index 4052576..e2226e3 100644 --- a/condition/condition.go +++ b/condition/condition.go @@ -57,7 +57,7 @@ func NewExprCondition(expression string) (Condition, error) { expr.AllowUndefinedVariables(), expr.AsBool(), } - // 注入 StreamSQL 内置函数,使 WHERE/HAVING/OVER-WHEN 等条件可调用 to_seconds/now/abs 等 + // Injecting built-in StreamSQL functions to enable conditions like WHERE/HAVING/OVER-WHEN to call to_seconds/now/abs, etc options = append(options, functions.GetExprBridge().RegisterStreamSQLFunctionsToExpr()...) program, err := expr.Compile(expression, options...) diff --git a/condition/condition_test.go b/condition/condition_test.go index 9d50c78..caa1022 100644 --- a/condition/condition_test.go +++ b/condition/condition_test.go @@ -7,7 +7,7 @@ import ( "github.com/stretchr/testify/require" ) -// TestNewExprCondition 测试创建表达式条件 +// TestNewExprCondition Tests to create expression conditions func TestNewExprCondition(t *testing.T) { tests := []struct { name string @@ -55,7 +55,7 @@ func TestNewExprCondition(t *testing.T) { } } -// TestExprCondition_Evaluate 测试表达式条件求值 +// TestExprCondition_Evaluate Test expression conditional evaluation func TestExprCondition_Evaluate(t *testing.T) { tests := []struct { name string @@ -125,7 +125,7 @@ func TestExprCondition_Evaluate(t *testing.T) { } } -// TestExprCondition_IsNull 测试is_null函数 +// TestExprCondition_IsNull Test is_null function func TestExprCondition_IsNull(t *testing.T) { tests := []struct { name string @@ -177,7 +177,7 @@ func TestExprCondition_IsNull(t *testing.T) { } } -// TestExprCondition_LikeMatch 测试like_match函数 +// TestExprCondition_LikeMatch Test like_match function func TestExprCondition_LikeMatch(t *testing.T) { tests := []struct { name string @@ -241,7 +241,7 @@ func TestExprCondition_LikeMatch(t *testing.T) { } } -// TestMatchesLikePattern 测试LIKE模式匹配函数 +// TestMatchesLikePattern tests the LIKE pattern matching function func TestMatchesLikePattern(t *testing.T) { tests := []struct { name string @@ -325,7 +325,7 @@ func TestMatchesLikePattern(t *testing.T) { } } -// TestExprCondition_ErrorHandling 测试错误处理 +// TestExprCondition_ErrorHandling Handling of test errors func TestExprCondition_ErrorHandling(t *testing.T) { tests := []struct { name string @@ -357,7 +357,7 @@ func TestExprCondition_ErrorHandling(t *testing.T) { t.Run(tt.name, func(t *testing.T) { cond, err := NewExprCondition(tt.expression) if err != nil { - // 如果编译失败,跳过这个测试 + // If the compilation fails, skip this test t.Skipf("Expression compilation failed: %v", err) return } @@ -369,7 +369,7 @@ func TestExprCondition_ErrorHandling(t *testing.T) { } } -// TestExprCondition_ComplexExpressions 测试复杂表达式 +// TestExprCondition_ComplexExpressions Test complex expressions func TestExprCondition_ComplexExpressions(t *testing.T) { tests := []struct { name string @@ -415,7 +415,7 @@ func TestExprCondition_ComplexExpressions(t *testing.T) { } } -// TestExprCondition_FunctionErrors 测试函数错误处理 +// TestExprCondition_FunctionErrors Error handling of test functions func TestExprCondition_FunctionErrors(t *testing.T) { tests := []struct { name string @@ -442,7 +442,7 @@ func TestExprCondition_FunctionErrors(t *testing.T) { } } -// TestExprCondition_AdvancedFeatures 测试高级功能 +// TestExprCondition_AdvancedFeatures Test advanced features func TestExprCondition_AdvancedFeatures(t *testing.T) { tests := []struct { name string @@ -469,7 +469,7 @@ func TestExprCondition_AdvancedFeatures(t *testing.T) { } } -// TestExprCondition_EdgeCases 测试边界情况 +// TestExprCondition_EdgeCases Test boundary conditions func TestExprCondition_EdgeCases(t *testing.T) { tests := []struct { name string diff --git a/docs/CUSTOM_FUNCTIONS_GUIDE.md b/docs/CUSTOM_FUNCTIONS_GUIDE.md index 06dcfc7..30bca97 100644 --- a/docs/CUSTOM_FUNCTIONS_GUIDE.md +++ b/docs/CUSTOM_FUNCTIONS_GUIDE.md @@ -1,104 +1,104 @@ -# StreamSQL 自定义函数开发指南 - -## 🚀 概述 - -StreamSQL 提供了强大而灵活的自定义函数系统,支持用户根据业务需求扩展各种类型的函数,包括数学函数、字符串函数、聚合函数、分析函数等。 - -## 📋 函数类型分类 - -### 内置函数类型 - -```go -const ( - TypeAggregation FunctionType = "aggregation" // 聚合函数 - TypeWindow FunctionType = "window" // 窗口函数 - TypeDateTime FunctionType = "datetime" // 时间日期函数 - TypeConversion FunctionType = "conversion" // 转换函数 - TypeMath FunctionType = "math" // 数学函数 - TypeString FunctionType = "string" // 字符串函数 - TypeAnalytical FunctionType = "analytical" // 分析函数 - TypeCustom FunctionType = "custom" // 用户自定义函数 -) -``` - -## 🛠️ 自定义函数实现方式 - -### 方式一:快速注册(推荐简单函数) - -```go -import "github.com/rulego/streamsql/functions" - -// 注册一个简单的数学函数 -err := functions.RegisterCustomFunction( - "double", // 函数名 - functions.TypeMath, // 函数类型 - "数学函数", // 分类描述 - "将数值乘以2", // 函数描述 - 1, // 最少参数个数 - 1, // 最多参数个数 - func(ctx *functions.FunctionContext, args []interface{}) (interface{}, error) { - val, err := cast.ToFloat64E(args[0]) - if err != nil { +# StreamSQL Custom Function Development Guide + +## 🚀 Overview + +StreamSQL provides a powerful and flexible custom function system, supporting users in extending various types of functions according to business needs, including mathematical functions, string functions, aggregation functions, analysis functions, and more. + +## 📋 Classification of function types + +### Built-in function types + +```go +const ( + TypeAggregation FunctionType = "aggregation" // Aggregate function + TypeWindow FunctionType = "window" // Window function + TypeDateTime FunctionType = "datetime" // Date/time function + TypeConversion FunctionType = "conversion" // Conversion function + TypeMath FunctionType = "math" // Mathematical functions + TypeString FunctionType = "string" // String function + TypeAnalytical FunctionType = "analytical" // Analytical function + TypeCustom FunctionType = "custom" // User-defined functions +) +``` + +## 🛠️ Custom Function Implementation + +### Method 1: Quick Registration (Simple Recommended Function) + +```go +import "github.com/rulego/streamsql/functions" + +// Register a simple mathematical function +err := functions.RegisterCustomFunction( + "double", // Function name + functions.TypeMath, // Function type + "数学函数", // Classification description + "将数值乘以2", // Function description + 1, // Minimum number of parameters + 1, // Maximum number of parameters + func(ctx *functions.FunctionContext, args []interface{}) (interface{}, error) { + val, err := cast.ToFloat64E(args[0]) + if err != nil { return nil, err } return val * 2, nil }, -) -``` - -### 方式二:完整结构体实现(推荐复杂函数) - -```go -// 1. 定义函数结构体 -type AdvancedMathFunction struct { - *functions.BaseFunction - // 可以添加状态变量 - cache map[string]interface{} -} - -// 2. 实现构造函数 -func NewAdvancedMathFunction() *AdvancedMathFunction { - return &AdvancedMathFunction{ - BaseFunction: functions.NewBaseFunction( - "advanced_calc", // 函数名 - functions.TypeMath, // 函数类型 - "高级数学函数", // 分类 - "高级数学计算", // 描述 - 2, // 最少参数 - 3, // 最多参数 - ), - cache: make(map[string]interface{}), - } -} - -// 3. 实现验证方法(可选,如有特殊验证需求) -func (f *AdvancedMathFunction) Validate(args []interface{}) error { - if err := f.ValidateArgCount(args); err != nil { - return err - } - - // 自定义验证逻辑 - if len(args) >= 2 { - if _, err := cast.ToFloat64E(args[0]); err != nil { - return fmt.Errorf("第一个参数必须是数值") +) +``` + +### Method 2: Complete Struct Implementation (Recommended Complex Functions) + +```go +// 1. Define the function structure +type AdvancedMathFunction struct { + *functions.BaseFunction + // You can add status variables + cache map[string]interface{} +} + +// 2. Implement constructors +func NewAdvancedMathFunction() *AdvancedMathFunction { + return &AdvancedMathFunction{ + BaseFunction: functions.NewBaseFunction( + "advanced_calc", // Function name + functions.TypeMath, // Function type + "高级数学函数", // Classification + "高级数学计算", // Description + 2, // Minimum parameters + 3, // The most parameters + ), + cache: make(map[string]interface{}), + } +} + +// 3. Implement verification methods (optional, if special verification requirements exist) +func (f *AdvancedMathFunction) Validate(args []interface{}) error { + if err := f.ValidateArgCount(args); err != nil { + return err + } + + // Custom verification logic + if len(args) >= 2 { + if _, err := cast.ToFloat64E(args[0]); err != nil { + return fmt.Errorf("第一个参数必须是数值") } if _, err := cast.ToFloat64E(args[1]); err != nil { return fmt.Errorf("第二个参数必须是数值") } } - return nil -} - -// 4. 实现执行方法 -func (f *AdvancedMathFunction) Execute(ctx *functions.FunctionContext, args []interface{}) (interface{}, error) { - a, _ := cast.ToFloat64E(args[0]) - b, _ := cast.ToFloat64E(args[1]) - - operation := "add" // 默认操作 - if len(args) > 2 { - op, err := cast.ToStringE(args[2]) - if err == nil { + return nil +} + +// 4. Implement the execution method +func (f *AdvancedMathFunction) Execute(ctx *functions.FunctionContext, args []interface{}) (interface{}, error) { + a, _ := cast.ToFloat64E(args[0]) + b, _ := cast.ToFloat64E(args[1]) + + operation := "add" // Default operation + if len(args) > 2 { + op, err := cast.ToStringE(args[2]) + if err == nil { operation = op } } @@ -112,24 +112,24 @@ func (f *AdvancedMathFunction) Execute(ctx *functions.FunctionContext, args []in return math.Pow(a, b), nil default: return nil, fmt.Errorf("不支持的操作: %s", operation) - } -} - -// 5. 注册函数 -func init() { - functions.Register(NewAdvancedMathFunction()) -} -``` - -## 🎯 各类型函数实现示例 - -### 1. 数学函数示例 - -```go -// 距离计算函数 -func RegisterDistanceFunction() error { - return functions.RegisterCustomFunction( - "distance", + } +} + +// 5. Register functions +func init() { + functions.Register(NewAdvancedMathFunction()) +} +``` + +## 🎯 Examples of Function Type Implementations + +### 1. Mathematical function example + +```go +// Distance calculation function +func RegisterDistanceFunction() error { + return functions.RegisterCustomFunction( + "distance", functions.TypeMath, "几何数学", "计算两点间距离", @@ -147,20 +147,20 @@ func RegisterDistanceFunction() error { distance := math.Sqrt(math.Pow(x2-x1, 2) + math.Pow(y2-y1, 2)) return distance, nil }, - ) -} - -// SQL使用示例: -// SELECT device, distance(lat1, lon1, lat2, lon2) as dist FROM stream -``` - -### 2. 字符串函数示例 - -```go -// JSON提取函数 -func RegisterJsonExtractFunction() error { - return functions.RegisterCustomFunction( - "json_extract", + ) +} + +// SQL usage examples: +// SELECT device, distance(lat1, lon1, lat2, lon2) as dist FROM stream +``` + +### 2. Example of string function + +```go +// JSON extraction function +func RegisterJsonExtractFunction() error { + return functions.RegisterCustomFunction( + "json_extract", functions.TypeString, "JSON处理", "从JSON字符串中提取字段值", @@ -174,31 +174,31 @@ func RegisterJsonExtractFunction() error { var data map[string]interface{} if err := json.Unmarshal([]byte(jsonStr), &data); err != nil { - return nil, fmt.Errorf("invalid JSON: %v", err) - } - - // 简单路径提取(可扩展为复杂JSONPath) - value, exists := data[path] - if !exists { - return nil, nil + return nil, fmt.Errorf("invalid JSON: %v", err) + } + + // Simple Path Extraction (Can Be Expanded to Complex JSONPath) + value, exists := data[path] + if !exists { + return nil, nil } return value, nil }, - ) -} - -// SQL使用示例: -// SELECT device, json_extract(metadata, 'version') as version FROM stream -``` - -### 3. 时间日期函数示例 - -```go -// 时间格式化函数 -func RegisterDateFormatFunction() error { - return functions.RegisterCustomFunction( - "date_format", + ) +} + +// SQL usage examples: +// SELECT device, json_extract(metadata, 'version') as version FROM stream +``` + +### 3. Example of the time-date function + +```go +// Time formatting function +func RegisterDateFormatFunction() error { + return functions.RegisterCustomFunction( + "date_format", functions.TypeDateTime, "时间格式化", "格式化时间戳为指定格式", @@ -209,13 +209,13 @@ func RegisterDateFormatFunction() error { format, err := cast.ToStringE(args[1]) if err != nil { return nil, err } - - t := time.Unix(timestamp, 0) - - // 支持常见格式 - switch format { - case "YYYY-MM-DD": - return t.Format("2006-01-02"), nil + + t := time.Unix(timestamp, 0) + + // Supports common formats + switch format { + case "YYYY-MM-DD": + return t.Format("2006-01-02"), nil case "YYYY-MM-DD HH:mm:ss": return t.Format("2006-01-02 15:04:05"), nil case "RFC3339": @@ -224,20 +224,20 @@ func RegisterDateFormatFunction() error { return t.Format(format), nil } }, - ) -} - -// SQL使用示例: -// SELECT device, date_format(timestamp, 'YYYY-MM-DD') as date FROM stream -``` - -### 4. 转换函数示例 - -```go -// IP地址转换函数 -func RegisterIpToIntFunction() error { - return functions.RegisterCustomFunction( - "ip_to_int", + ) +} + +// SQL usage examples: +// SELECT device, date_format(timestamp, 'YYYY-MM-DD') as date FROM stream +``` + +### 4. Example of a conversion function + +```go +// IP address translation function +func RegisterIpToIntFunction() error { + return functions.RegisterCustomFunction( + "ip_to_int", functions.TypeConversion, "网络转换", "将IP地址转换为整数", @@ -248,53 +248,53 @@ func RegisterIpToIntFunction() error { ip := net.ParseIP(ipStr) if ip == nil { - return nil, fmt.Errorf("invalid IP address: %s", ipStr) - } - - // 转换为IPv4 - ip = ip.To4() - if ip == nil { - return nil, fmt.Errorf("not an IPv4 address: %s", ipStr) + return nil, fmt.Errorf("invalid IP address: %s", ipStr) + } + + // Convert to IPv4 + ip = ip.To4() + if ip == nil { + return nil, fmt.Errorf("not an IPv4 address: %s", ipStr) } return int64(ip[0])<<24 + int64(ip[1])<<16 + int64(ip[2])<<8 + int64(ip[3]), nil }, - ) -} - -// SQL使用示例: -// SELECT device, ip_to_int(client_ip) as ip_int FROM stream -``` - -### 5. 自定义聚合函数示例 - -聚合函数实现 `AggregatorFunction` 接口(`New`/`Add`/`Result`/`Reset`/`Clone`),用 `functions.Register` 注册一处即可——适配器自动接通,无需 `aggregator.Register`。 - -```go -import ( + ) +} + +// SQL usage examples: +// SELECT device, ip_to_int(client_ip) as ip_int FROM stream +``` + +### 5. Custom aggregator function example + +The aggregator function implements `AggregatorFunction` interfaces (`New` / `Add` / `Result` / `Reset` / `Clone`), registering one place with `functions. Register` — adapters automatically connect without `aggregator. Register`. + +```go +import ( "sort" "github.com/rulego/streamsql/functions" - "github.com/rulego/streamsql/utils/cast" -) - -// MedianAgg 完整实现 AggregatorFunction -type MedianAgg struct { - *functions.BaseFunction - values []float64 + "github.com/rulego/streamsql/utils/cast" +) + +// MedianAgg Fully implement AggregatorFunction +type MedianAgg struct { + *functions.BaseFunction + values []float64 } func NewMedianAgg() *MedianAgg { return &MedianAgg{BaseFunction: functions.NewBaseFunction( "median_agg", functions.TypeAggregation, "统计聚合", "计算中位数", 1, -1)} } - -func (f *MedianAgg) Validate(args []any) error { return f.ValidateArgCount(args) } -func (f *MedianAgg) Execute(ctx *functions.FunctionContext, args []any) (any, error) { - return nil, nil // 聚合走 Add/Result,Execute 仅满足接口 -} -func (f *MedianAgg) New() functions.AggregatorFunction { return &MedianAgg{BaseFunction: f.BaseFunction} } -func (f *MedianAgg) Add(value any) { + +func (f *MedianAgg) Validate(args []any) error { return f.ValidateArgCount(args) } +func (f *MedianAgg) Execute(ctx *functions.FunctionContext, args []any) (any, error) { + return nil, nil // Aggregation follows Add/Result, Execute only meets interface requirements +} +func (f *MedianAgg) New() functions.AggregatorFunction { return &MedianAgg{BaseFunction: f.BaseFunction} } +func (f *MedianAgg) Add(value any) { if v, err := cast.ToFloat64E(value); err == nil { f.values = append(f.values, v) } @@ -318,113 +318,113 @@ func (f *MedianAgg) Clone() functions.AggregatorFunction { } func init() { - functions.Register(NewMedianAgg()) -} - -// SQL使用示例: -// SELECT device, median_agg(temperature) as median_temp FROM stream GROUP BY device -``` - -## 📊 函数管理功能 - -### 查看已注册函数 - -```go -// 列出所有函数 -allFunctions := functions.ListAll() -for name, fn := range allFunctions { - fmt.Printf("函数名: %s, 类型: %s, 描述: %s\n", - name, fn.GetType(), fn.GetDescription()) -} - -// 按类型查看函数 -mathFunctions := functions.GetByType(functions.TypeMath) -for _, fn := range mathFunctions { - fmt.Printf("数学函数: %s - %s\n", fn.GetName(), fn.GetDescription()) -} - -// 检查函数是否存在 -if fn, exists := functions.Get("my_function"); exists { - fmt.Printf("函数存在: %s\n", fn.GetDescription()) -} -``` - -### 注销函数 - -```go -// 注销自定义函数 -success := functions.Unregister("my_custom_function") -if success { - fmt.Println("函数注销成功") -} -``` - -## 🎯 最佳实践 - -### 1. 错误处理 - -```go -func (f *MyFunction) Execute(ctx *functions.FunctionContext, args []interface{}) (interface{}, error) { - // 1. 参数验证 - if len(args) == 0 { - return nil, fmt.Errorf("至少需要一个参数") - } - - // 2. 类型转换 - val, err := cast.ToFloat64E(args[0]) - if err != nil { - return nil, fmt.Errorf("参数类型错误: %v", err) - } - - // 3. 业务逻辑验证 - if val < 0 { - return nil, fmt.Errorf("参数值必须为正数") - } - - // 4. 计算逻辑 - result := math.Sqrt(val) - - return result, nil -} -``` - -### 2. 性能优化 - -```go -type CachedFunction struct { + functions.Register(NewMedianAgg()) +} + +// SQL usage examples: +// SELECT device, median_agg(temperature) as median_temp FROM stream GROUP BY device +``` + +## 📊 Function Management Features + +### Check registered functions + +```go +// List all functions +allFunctions := functions.ListAll() +for name, fn := range allFunctions { + fmt.Printf("函数名: %s, 类型: %s, 描述: %s\n", + name, fn.GetType(), fn.GetDescription()) +} + +// View functions by type +mathFunctions := functions.GetByType(functions.TypeMath) +for _, fn := range mathFunctions { + fmt.Printf("数学函数: %s - %s\n", fn.GetName(), fn.GetDescription()) +} + +// Check if the function exists +if fn, exists := functions.Get("my_function"); exists { + fmt.Printf("函数存在: %s\n", fn.GetDescription()) +} +``` + +### Cancel Function + +```go +// Delete custom functions +success := functions.Unregister("my_custom_function") +if success { + fmt.Println("函数注销成功") +} +``` + +## 🎯 Best Practices + +### 1. Error handling + +```go +func (f *MyFunction) Execute(ctx *functions.FunctionContext, args []interface{}) (interface{}, error) { + // 1. Parameter validation + if len(args) == 0 { + return nil, fmt.Errorf("至少需要一个参数") + } + + // 2. Type conversion + val, err := cast.ToFloat64E(args[0]) + if err != nil { + return nil, fmt.Errorf("参数类型错误: %v", err) + } + + // 3. Business logic validation + if val < 0 { + return nil, fmt.Errorf("参数值必须为正数") + } + + // 4. Computational logic + result := math.Sqrt(val) + + return result, nil +} +``` + +### 2. Performance optimization + +```go +type CachedFunction struct { *functions.BaseFunction cache map[string]interface{} mutex sync.RWMutex -} - -func (f *CachedFunction) Execute(ctx *functions.FunctionContext, args []interface{}) (interface{}, error) { - // 生成缓存key - key := fmt.Sprintf("%v", args) - - // 检查缓存 - f.mutex.RLock() - if cached, exists := f.cache[key]; exists { - f.mutex.RUnlock() +} + +func (f *CachedFunction) Execute(ctx *functions.FunctionContext, args []interface{}) (interface{}, error) { + // Generate cache key + key := fmt.Sprintf("%v", args) + + // Check the cache + f.mutex.RLock() + if cached, exists := f.cache[key]; exists { + f.mutex.RUnlock() return cached, nil - } - f.mutex.RUnlock() - - // 计算结果 - result := f.calculate(args) - - // 存储到缓存 - f.mutex.Lock() - f.cache[key] = result - f.mutex.Unlock() + } + f.mutex.RUnlock() + + // Calculation results + result := f.calculate(args) + + // Store to cache + f.mutex.Lock() + f.cache[key] = result + f.mutex.Unlock() return result, nil -} -``` - -### 3. 状态管理 - -```go -type StatefulFunction struct { +} +``` + +### 3. Status management + +```go +type StatefulFunction struct { *functions.BaseFunction counter int64 mutex sync.Mutex @@ -436,40 +436,40 @@ func (f *StatefulFunction) Execute(ctx *functions.FunctionContext, args []interf f.counter++ return f.counter, nil -} -``` - -## 🚨 注意事项 - -1. **线程安全**: 函数可能在多线程环境下并发执行,确保线程安全 -2. **错误处理**: 总是返回有意义的错误信息 -3. **类型转换**: 使用框架提供的转换函数进行类型转换 -4. **性能考虑**: 避免在函数中执行耗时操作,考虑使用缓存 -5. **资源管理**: 注意资源的申请和释放 -6. **命名规范**: 使用清晰、描述性的函数名 - -## 📝 测试你的自定义函数 - -```go -func TestMyCustomFunction(t *testing.T) { - // 注册函数 - err := functions.RegisterCustomFunction("test_func", /* ... */) - assert.NoError(t, err) - defer functions.Unregister("test_func") - - // 获取函数 - fn, exists := functions.Get("test_func") - assert.True(t, exists) - - // 测试执行 - ctx := &functions.FunctionContext{ - Data: make(map[string]interface{}), - } +} +``` + +## 🚨 Notes + +1. **Thread Safety**: Functions may execute concurrently in multithreaded environments to ensure thread safety +2. **Error handling**: Always returns meaningful error messages +3. **Type Conversion**: Use the conversion function provided by the framework to perform type conversion +4. **Performance Considerations**: Avoid time-consuming operations in functions and consider using caches +5. **Resource Management**: Pay attention to resource application and release +6. **Naming conventions**: Use clear and descriptive function names + +## 📝 Test your custom function + +```go +func TestMyCustomFunction(t *testing.T) { + // Register the function + err := functions.RegisterCustomFunction("test_func", /* ... */) + assert.NoError(t, err) + defer functions.Unregister("test_func") + + // Get the function + fn, exists := functions.Get("test_func") + assert.True(t, exists) + + // Test execution + ctx := &functions.FunctionContext{ + Data: make(map[string]interface{}), + } result, err := fn.Execute(ctx, []interface{}{10.0}) assert.NoError(t, err) assert.Equal(t, expectedResult, result) -} -``` - -通过这个指南,你可以轻松扩展StreamSQL的功能,实现各种自定义函数来满足特定的业务需求。 \ No newline at end of file +} +``` + +With this guide, you can easily extend StreamSQL's features and implement various custom functions to meet specific business needs. diff --git a/docs/FUNCTIONS.md b/docs/FUNCTIONS.md index 4e44016..0fc2eba 100644 --- a/docs/FUNCTIONS.md +++ b/docs/FUNCTIONS.md @@ -1,72 +1,72 @@ -# StreamSQL 函数系统 - -StreamSQL 现已支持强大的函数系统,允许在 SQL 查询中使用各种内置函数和自定义函数。 - -## 🚀 主要特性 - -### 1. 模块化函数架构 -- **函数注册器**:统一的函数注册和管理系统 -- **类型安全**:强类型参数验证和转换 -- **可扩展性**:支持运行时注册自定义函数 -- **分类管理**:按功能类型组织函数 - -### 2. 内置函数类别 - -#### 数学函数 (TypeMath) -- `ABS(x)` - 绝对值 -- `SQRT(x)` - 平方根 - -#### 字符串函数 (TypeString) -- `CONCAT(str1, str2, ...)` - 字符串连接 -- `LENGTH(str)` - 字符串长度 -- `UPPER(str)` - 转大写 -- `LOWER(str)` - 转小写 - -#### 转换函数 (TypeConversion) -- `CAST(value, type)` - 类型转换 -- `HEX2DEC(hexStr)` - 十六进制转十进制 -- `DEC2HEX(number)` - 十进制转十六进制 - -#### 时间日期函数 (TypeDateTime) -- `NOW()` - 当前时间戳 - -### 3. 表达式引擎增强 -- 支持函数调用的复杂表达式 -- 运算符优先级处理 -- 括号分组支持 -- 自动类型转换 - -## 📝 使用示例 - -### 基本函数使用 - -```sql --- 数学函数 -SELECT device, ABS(temperature - 20) as deviation -FROM stream; - --- 字符串函数 -SELECT CONCAT(device, '_processed') as processed_name -FROM stream; - --- 表达式中的函数 -SELECT device, AVG(ABS(temperature - 20)) as avg_deviation -FROM stream -GROUP BY device, TumblingWindow('1s'); -``` - -### 自定义函数注册 - -```go -import ( +# StreamSQL Function system + +StreamSQL now supports a powerful function system, allowing the use of various built-in and custom functions in SQL queries. + +## 🚀 Main Features + +### 1. Modular function architecture +- **Function Registerer**: A unified system for function registration and management +- **Type Safety**: Strong type parameter validation and conversion +- **Scalability**: Supports runtime registration of custom functions +- **Category Management**: Organize functions by function type + +### 2. Built-in function categories + +#### Mathematical Functions (TypeMath) +- `ABS(x)` - absolute value +- `SQRT(x)` - square root + +#### String Function (TypeString) +- `CONCAT(str1, str2,.)` - String concatenation +- `LENGTH(str)` - String length +- `UPPER(str)` - Capitalized +- `LOWER(str)` - lowercase + +#### Transformation Function (TypeConversion) +- `CAST(value, type)` - Type conversion +- `HEX2DEC(hexStr)` - Hexadecimal converted to decimal +- `DEC2HEX(number)` - Decimal converted to hexadecimal + +#### Time-Date Function (TypeDateTime) +- `NOW()` - Current timestamp + +### 3. Expression engine enhancement +- Complex expressions that support function calls +- Operator priority handling +- Bracket grouping support +- Automatic type conversion + +## 📝 Usage Examples + +### Basic Function Usage + +```sql +-- Mathematical functions +SELECT device, ABS(temperature - 20) as deviation +FROM stream; + +-- String function +SELECT CONCAT(device, '_processed') as processed_name +FROM stream; + +-- Functions in expressions +SELECT device, AVG(ABS(temperature - 20)) as avg_deviation +FROM stream +GROUP BY device, TumblingWindow('1s'); +``` + +### Custom function registration + +```go +import ( "github.com/rulego/streamsql/functions" - "github.com/rulego/streamsql/utils/cast" -) - -// 注册华氏度转摄氏度函数 -err := functions.RegisterCustomFunction( - "fahrenheit_to_celsius", - functions.TypeCustom, + "github.com/rulego/streamsql/utils/cast" +) + +// Register the Fahrenheit to Celsius function +err := functions.RegisterCustomFunction( + "fahrenheit_to_celsius", + functions.TypeCustom, "温度转换", "华氏度转摄氏度", 1, 1, @@ -76,73 +76,73 @@ err := functions.RegisterCustomFunction( return nil, err } celsius := (fahrenheit - 32) * 5 / 9 - return celsius, nil - }) - -// 在 SQL 中使用 -sql := ` - SELECT device, AVG(fahrenheit_to_celsius(temperature)) as avg_celsius - FROM stream + return celsius, nil + }) + +// Used in SQL +sql := ` + SELECT device, AVG(fahrenheit_to_celsius(temperature)) as avg_celsius + FROM stream GROUP BY device, TumblingWindow('2s') -` -``` - -### 复合表达式 - -```sql --- 复杂的数学表达式 -SELECT - device, - AVG(ABS(temperature - 20) * 1.8 + 32) as complex_calc +` +``` + +### Compound Expressions + +```sql +-- Complex mathematical expressions +SELECT + device, + AVG(ABS(temperature - 20) * 1.8 + 32) as complex_calc FROM stream -GROUP BY device, TumblingWindow('1s'); -``` - -## 🛠️ 函数开发 - -### 实现自定义函数 - -```go -// 1. 定义函数结构 -type MyCustomFunction struct { - *functions.BaseFunction -} - -// 2. 实现构造函数 -func NewMyCustomFunction() *MyCustomFunction { - return &MyCustomFunction{ - BaseFunction: functions.NewBaseFunction( +GROUP BY device, TumblingWindow('1s'); +``` + +## 🛠️ Function Development + +### Implementing custom functions + +```go +// 1. Define the function structure +type MyCustomFunction struct { + *functions.BaseFunction +} + +// 2. Implement constructors +func NewMyCustomFunction() *MyCustomFunction { + return &MyCustomFunction{ + BaseFunction: functions.NewBaseFunction( "my_func", - functions.TypeCustom, - "自定义分类", - "函数描述", - 1, 3, // 最少1个参数,最多3个参数 - ), - } -} - -// 3. 实现验证方法 -func (f *MyCustomFunction) Validate(args []interface{}) error { - return f.ValidateArgCount(args) -} - -// 4. 实现执行方法 -func (f *MyCustomFunction) Execute(ctx *functions.FunctionContext, args []interface{}) (interface{}, error) { - // 实现具体逻辑 - return result, nil -} - -// 5. 注册函数 -functions.Register(NewMyCustomFunction()) -``` - -### 便捷注册方式 - -```go -// 使用便捷方法注册函数 -err := functions.RegisterCustomFunction( - "double", - functions.TypeCustom, + functions.TypeCustom, + "自定义分类", + "函数描述", + 1, 3, // Minimum 1 parameter, maximum 3 parameters + ), + } +} + +// 3. Implement verification methods +func (f *MyCustomFunction) Validate(args []interface{}) error { + return f.ValidateArgCount(args) +} + +// 4. Implement the execution method +func (f *MyCustomFunction) Execute(ctx *functions.FunctionContext, args []interface{}) (interface{}, error) { + // Implement concrete logic + return result, nil +} + +// 5. Register functions +functions.Register(NewMyCustomFunction()) +``` + +### Convenient Registration Methods + +```go +// Use an easy way to register the function +err := functions.RegisterCustomFunction( + "double", + functions.TypeCustom, "数学运算", "将数值乘以2", 1, 1, @@ -152,73 +152,73 @@ err := functions.RegisterCustomFunction( return nil, err } return val * 2, nil - }) -``` - -## 🧪 测试 - -### 运行函数系统测试 -```bash -go test ./functions -v -``` - -### 运行集成测试 -```bash -go test -v -run TestExpressionInAggregation -``` - -## 📋 支持的数据类型 - -函数系统支持以下数据类型的自动转换: - -- **数值类型**: `int`, `int32`, `int64`, `uint`, `uint32`, `uint64`, `float32`, `float64` -- **字符串类型**: `string` -- **布尔类型**: `bool` -- **自动转换**: 字符串数值自动转换为相应的数值类型 - -## 🔧 类型转换工具 - -```go -// 使用内置转换函数 -val, err := cast.ToFloat64E(someValue) -str, err := cast.ToStringE(someValue) -num, err := cast.ToInt64E(someValue) -flag, err := cast.ToBoolE(someValue) -``` - -## 📈 性能考虑 - -- **函数注册**: 一次性注册,运行时无开销 -- **类型转换**: 高效的类型检查和转换 -- **表达式缓存**: 表达式解析结果可复用 -- **并发安全**: 函数注册器支持并发访问 - -## 🌟 路线图 - -已实现的功能: -- ✅ SELECT DISTINCT -- ✅ LIMIT 子句 -- ✅ HAVING 子句 -- ✅ SESSION 窗口 -- ✅ 函数参数支持表达式运算 -- ✅ 统一函数注册系统 - -待实现的功能: -- 🔄 更多聚合函数(MEDIAN、STDDEV 等) -- 🔄 窗口函数(ROW_NUMBER、RANK 等) -- 🔄 更多时间日期函数 -- 🔄 正则表达式函数 -- 🔄 JSON 处理函数 - -## 🤝 贡献 - -欢迎提交新的函数实现!请遵循以下步骤: - -1. 在 `functions/` 目录中实现函数 -2. 添加相应的测试用例 -3. 更新文档 -4. 提交 Pull Request - ---- - -*StreamSQL 函数系统让流处理更加强大和灵活!* 🚀 \ No newline at end of file + }) +``` + +## 🧪 Testing + +### Running Function System Testing +```bash +go test ./functions -v +``` + +### Run Integration Testing +```bash +go test -v -run TestExpressionInAggregation +``` + +## 📋 Supported Data Types + +The function system supports automatic conversion of the following data types: + +- **Numeric types**: `int`, `int32`, `int64`, `uint`, `uint32`, `uint64`, `float32`, `float64` +- **String type**: `string` +- **Boolean type**: `bool` +- **Automatic Conversion**: Automatically converts string values into the corresponding value type + +## 🔧 Type conversion tool + +```go +// Use built-in conversion functions +val, err := cast.ToFloat64E(someValue) +str, err := cast.ToStringE(someValue) +num, err := cast.ToInt64E(someValue) +flag, err := cast.ToBoolE(someValue) +``` + +## 📈 Performance considerations + +- **Function Registration**: One-time registration, no overhead at runtime +- **Type Conversion**: Efficient type checking and conversion +- **Expression cache**: Expression parsing results can be reused +- **Concurrency Security**: The function registerer supports concurrent access + +## 🌟 Roadmap + +Implemented features: +- ✅ SELECT DISTINCT +- ✅ LIMIT Clause +- ✅ HAVING Clause +- ✅ SESSION window +- ✅ Function parameters support expression operations +- ✅ Unified function registration system + +Functions to be implemented: +- 🔄 More aggregate functions (MEDIAN, STDDEV, etc.) +- 🔄 Window functions (ROW_NUMBER, RANK, etc.) +- 🔄 More time-date functions +- 🔄 Regular expression function +- 🔄 JSON Handling functions + +## 🤝 Contribution + +Welcome to submit new function implementations! Please follow these steps: + +1. Implement the function in the `functions/` directory +2. Add the corresponding test cases +3. Update the documentation +4. Submit Pull Request + +--- + +*StreamSQL Function system makes stream processing more powerful and flexible! * 🚀 diff --git a/docs/FUNCTIONS_USAGE_GUIDE.md b/docs/FUNCTIONS_USAGE_GUIDE.md index b5c3c45..4175edd 100644 --- a/docs/FUNCTIONS_USAGE_GUIDE.md +++ b/docs/FUNCTIONS_USAGE_GUIDE.md @@ -1,756 +1,756 @@ -# StreamSQL 函数使用指南 +# StreamSQL Function usage guide -StreamSQL 具有丰富的内置函数,可以对数据执行各种计算。所有函数都支持在流式处理环境中使用,部分函数支持增量计算以提高性能。 +StreamSQL Rich built-in functions that can perform various calculations on data. All functions support use in streaming environments, with some supporting incremental computation to improve performance. -## 📊 聚合函数 +## 📊 Aggregation Function -聚合函数对一组值执行计算并返回单个值。聚合函数只能用在以下表达式中: -- SELECT 语句的 SELECT 列表(子查询或外部查询) -- HAVING 子句 +The aggregate function performs calculations on a set of values and returns a single value. Aggregation functions can only be used in the following expressions: +- SELECT list of SELECT statements (subqueries or external queries) +- HAVING Clause -### SUM - 求和函数 -**语法**: `sum(col)` -**描述**: 返回组中数值的总和。空值不参与计算。 -**增量计算**: ✅ 支持 -**示例**: +### SUM - Summation function +**Grammar**: `sum(col)` +**Description**: Returns the sum of values in the group. Null values are not involved in the calculation. +**Incremental Calculation**: ✅ Supported +**Example**: ```sql SELECT device, sum(temperature) as total_temp FROM stream GROUP BY device, TumblingWindow('10s') ``` -### AVG - 平均值函数 -**语法**: `avg(col)` -**描述**: 返回组中数值的平均值。空值不参与计算。 -**增量计算**: ✅ 支持 -**示例**: +### AVG - Average Function +**Grammar**: `avg(col)` +**Description**: Returns the average of the values in the group. Null values are not involved in the calculation. +**Incremental Calculation**: ✅ Supported +**Example**: ```sql SELECT device, avg(temperature) as avg_temp FROM stream GROUP BY device, TumblingWindow('10s') ``` -### COUNT - 计数函数 -**语法**: `count(*)` -**描述**: 返回组中的行数。 -**增量计算**: ✅ 支持 -**示例**: +### COUNT - Counting Function +**Grammar**: `count(*)` +**Description**: Returns the number of rows in the group. +**Incremental Calculation**: ✅ Supported +**Example**: ```sql SELECT device, count(*) as record_count FROM stream GROUP BY device, TumblingWindow('10s') ``` -### MIN - 最小值函数 -**语法**: `min(col)` -**描述**: 返回组中数值的最小值。空值不参与计算。 -**增量计算**: ✅ 支持 -**示例**: +### MIN - Minimum value function +**Grammar**: `min(col)` +**Description**: Returns the minimum value of the value in the group. Null values are not involved in the calculation. +**Incremental Calculation**: ✅ Supported +**Example**: ```sql SELECT device, min(temperature) as min_temp FROM stream GROUP BY device, TumblingWindow('10s') ``` -### MAX - 最大值函数 -**语法**: `max(col)` -**描述**: 返回组中数值的最大值。空值不参与计算。 -**增量计算**: ✅ 支持 -**示例**: +### MAX - Maximum value function +**Grammar**: `max(col)` +**Description**: Returns the maximum value of the value in the group. Null values are not involved in the calculation. +**Incremental Calculation**: ✅ Supported +**Example**: ```sql SELECT device, max(temperature) as max_temp FROM stream GROUP BY device, TumblingWindow('10s') ``` -### COLLECT - 收集函数 -**语法**: `collect(col)` -**描述**: 获取当前窗口所有消息的列值组成的数组。 -**增量计算**: ✅ 支持 -**示例**: +### COLLECT - Collection function +**Grammar**: `collect(col)` +**Description**: Retrieves an array of column values for all messages in the current window. +**Incremental Calculation**: ✅ Supported +**Example**: ```sql SELECT device, collect(temperature) as temp_values FROM stream GROUP BY device, TumblingWindow('10s') ``` -### LAST_VALUE - 最后值函数 -**语法**: `last_value(col)` -**描述**: 返回组中最后一行的值。 -**增量计算**: ✅ 支持 -**示例**: +### LAST_VALUE - The last value function +**Grammar**: `last_value(col)` +**Description**: Returns the value of the last row in the group. +**Incremental Calculation**: ✅ Supported +**Example**: ```sql SELECT device, last_value(temperature) as last_temp FROM stream GROUP BY device, TumblingWindow('10s') ``` -### MERGE_AGG - 合并聚合函数 -**语法**: `merge_agg(col)` -**描述**: 将组中的值合并为单个值。对于对象类型,合并所有键值对;对于其他类型,用逗号连接。 -**增量计算**: ✅ 支持 -**示例**: +### MERGE_AGG - Merge Aggregation Function +**Grammar**: `merge_agg(col)` +**Description**: Merge values from a group into a single value. For object types, merge all key-value pairs; For other types, connect with commas. +**Incremental Calculation**: ✅ Supported +**Example**: ```sql SELECT device, merge_agg(status) as all_status FROM stream GROUP BY device, TumblingWindow('10s') ``` -### DEDUPLICATE - 去重函数 -**语法**: `deduplicate(col, false)` -**描述**: 返回当前组去重的结果,通常用在窗口中。第二个参数指定是否返回全部结果。 -**增量计算**: ✅ 支持 -**示例**: +### DEDUPLICATE - Deduplication function +**Grammar**: `deduplicate(col, false)` +**Description**: Returns the result of deduplication from the current group, usually used in windows. The second parameter specifies whether to return all results. +**Incremental Calculation**: ✅ Supported +**Example**: ```sql SELECT device, deduplicate(temperature, true) as unique_temps FROM stream GROUP BY device, TumblingWindow('10s') ``` -### STDDEV - 标准差函数 -**语法**: `stddev(col)` -**描述**: 返回组中所有值的总体标准差。空值不参与计算。 -**增量计算**: ✅ 支持(使用韦尔福德算法优化) -**示例**: +### STDDEV - Standard Deviation Function +**Grammar**: `stddev(col)` +**Description**: Returns the population standard deviation of all values in the group. Null values are not involved in the calculation. +**Incremental Computation**: ✅ Supported (optimized using the Welford algorithm) +**Example**: ```sql SELECT device, stddev(temperature) as temp_stddev FROM stream GROUP BY device, TumblingWindow('10s') ``` -### STDDEVS - 样本标准差函数 -**语法**: `stddevs(col)` -**描述**: 返回组中所有值的样本标准差。空值不参与计算。 -**增量计算**: ✅ 支持(使用韦尔福德算法优化) -**示例**: +### STDDEVS - Sample Standard Deviation Function +**Grammar**: `stddevs(col)` +**Description**: Returns the sample standard deviation of all values in the group. Null values are not involved in the calculation. +**Incremental Computation**: ✅ Supported (optimized using the Welford algorithm) +**Example**: ```sql SELECT device, stddevs(temperature) as temp_sample_stddev FROM stream GROUP BY device, TumblingWindow('10s') ``` -### VAR - 方差函数 -**语法**: `var(col)` -**描述**: 返回组中所有值的总体方差。空值不参与计算。 -**增量计算**: ✅ 支持(使用韦尔福德算法优化) -**示例**: +### VAR - Variance Function +**Grammar**: `var(col)` +**Description**: Returns the population variance of all values in the group. Null values are not involved in the calculation. +**Incremental Computation**: ✅ Supported (optimized using the Welford algorithm) +**Example**: ```sql SELECT device, var(temperature) as temp_variance FROM stream GROUP BY device, TumblingWindow('10s') ``` -### VARS - 样本方差函数 -**语法**: `vars(col)` -**描述**: 返回组中所有值的样本方差。空值不参与计算。 -**增量计算**: ✅ 支持(使用韦尔福德算法优化) -**示例**: +### VARS - Sample variance function +**Grammar**: `vars(col)` +**Description**: Returns the sample variance of all non-NULL values in the group. +**Incremental Computation**: ✅ Supported (optimized using the Welford algorithm) +**Example**: ```sql SELECT device, vars(temperature) as temp_sample_variance FROM stream GROUP BY device, TumblingWindow('10s') ``` -### MEDIAN - 中位数函数 -**语法**: `median(col)` -**描述**: 返回组中所有值的中位数。空值不参与计算。 -**增量计算**: ✅ 支持 -**示例**: +### MEDIAN - Median function +**Grammar**: `median(col)` +**Description**: Returns the median of all values in the group. Null values are not involved in the calculation. +**Incremental Calculation**: ✅ Supported +**Example**: ```sql SELECT device, median(temperature) as temp_median FROM stream GROUP BY device, TumblingWindow('10s') ``` -### PERCENTILE - 百分位数函数 -**语法**: `percentile(col, 0.5)` -**描述**: 返回组中所有值的指定百分位数。第二个参数指定百分位数的值,取值范围为 0.0 ~ 1.0。 -**增量计算**: ✅ 支持 -**示例**: +### PERCENTILE - Percentile Function +**Grammar**: `percentile(col, 0.5)` +**Description**: Returns the specified percentile of all values in the group. The second parameter specifies the percentile value, with a range of 0.0 ~ 1.0. +**Incremental Calculation**: ✅ Supported +**Example**: ```sql SELECT device, percentile(temperature, 0.95) as temp_p95 FROM stream GROUP BY device, TumblingWindow('10s') ``` -## 🔍 分析函数 +## 🔍 Analyze the function -分析函数用于在数据流中进行复杂的分析计算,支持状态管理和历史数据访问。 +Analysis functions are used to perform complex analytical calculations within data streams, supporting state management and access to historical data. -### LAG - 滞后函数 -**语法**: `lag(col, offset, default_value)` -**描述**: 返回当前行之前的第N行的值。offset指定偏移量,default_value为默认值。 -**增量计算**: ✅ 支持 -**示例**: +### LAG - Lag function +**Grammar**: `lag(col, offset, default_value)` +**Description**: Returns the value of the N-th line before the current line. offset Specify the offset, default_value is the default value. +**Incremental Calculation**: ✅ Supported +**Example**: ```sql SELECT device, temperature, lag(temperature, 1) as prev_temp FROM stream GROUP BY device, TumblingWindow('10s') ``` -### LATEST - 最新值函数 -**语法**: `latest(col)` -**描述**: 返回指定列的最新值。 -**增量计算**: ✅ 支持 -**示例**: +### LATEST - Latest value function +**Grammar**: `latest(col)` +**Description**: Returns the latest value for the specified column. +**Incremental Calculation**: ✅ Supported +**Example**: ```sql SELECT device, latest(temperature) as current_temp FROM stream GROUP BY device, TumblingWindow('10s') ``` -### CHANGED_COL - 变化列函数 -**语法**: `changed_col(row_data)` -**描述**: 返回发生变化的列名数组。 -**增量计算**: ✅ 支持 -**示例**: +### CHANGED_COL - Change column function +**Grammar**: `changed_col(row_data)` +**Description**: Returns the column name array where it changed. +**Incremental Calculation**: ✅ Supported +**Example**: ```sql SELECT device, changed_col(*) as changed_columns FROM stream GROUP BY device, TumblingWindow('10s') ``` -### HAD_CHANGED - 变化检测函数 -**语法**: `had_changed(col)` -**描述**: 判断指定列的值是否发生变化,返回布尔值。 -**增量计算**: ✅ 支持 -**示例**: +### HAD_CHANGED - Change detection function +**Grammar**: `had_changed(col)` +**Description**: Check whether the value of a specified column has changed, and return a boolean value. +**Incremental Calculation**: ✅ Supported +**Example**: ```sql SELECT device, had_changed(status) as status_changed FROM stream GROUP BY device, TumblingWindow('10s') ``` -## 🪟 窗口函数 +## 🪟 Window function -窗口函数提供窗口相关的信息。 +The window function provides information related to the window. -### WINDOW_START - 窗口开始时间 -**语法**: `window_start()` -**描述**: 返回当前窗口的开始时间。 -**增量计算**: ✅ 支持 -**示例**: +### WINDOW_START - Window start time +**Grammar**: `window_start()` +**Description**: Returns the start time of the current window. +**Incremental Calculation**: ✅ Supported +**Example**: ```sql SELECT device, window_start() as window_begin, avg(temperature) as avg_temp FROM stream GROUP BY device, TumblingWindow('10s') ``` -### WINDOW_END - 窗口结束时间 -**语法**: `window_end()` -**描述**: 返回当前窗口的结束时间。 -**增量计算**: ✅ 支持 -**示例**: +### WINDOW_END - Window closing time +**Grammar**: `window_end()` +**Description**: Returns the end time of the current window. +**Incremental Calculation**: ✅ Supported +**Example**: ```sql SELECT device, window_end() as window_finish, avg(temperature) as avg_temp FROM stream GROUP BY device, TumblingWindow('10s') ``` -## 🧮 数学函数 +## 🧮 Mathematical Functions -数学函数用于数值计算。 +Mathematical functions are used for numerical calculations. -### ABS - 绝对值函数 -**语法**: `abs(number)` -**描述**: 返回数值的绝对值。 +### ABS - Absolute value function +**Grammar**: `abs(number)` +**Description**: Returns the absolute value of the value. -### SQRT - 平方根函数 -**语法**: `sqrt(number)` -**描述**: 返回数值的平方根。 +### SQRT - Square root function +**Grammar**: `sqrt(number)` +**Description**: Returns the square root of the value. -### POWER - 幂函数 -**语法**: `power(base, exponent)` -**描述**: 返回底数的指定次幂。 +### POWER - Power Function +**Grammar**: `power(base, exponent)` +**Description**: Returns the specified power of the base. -### CEILING - 向上取整函数 -**语法**: `ceiling(number)` -**描述**: 返回大于或等于指定数值的最小整数。 +### CEILING - The uptake function +**Grammar**: `ceiling(number)` +**Description**: Returns the smallest integer greater than or equal to the specified value. -### FLOOR - 向下取整函数 -**语法**: `floor(number)` -**描述**: 返回小于或等于指定数值的最大整数。 +### FLOOR - Downturn function +**Grammar**: `floor(number)` +**Description**: Returns the largest integer less than or equal to the specified value. -### ROUND - 四舍五入函数 -**语法**: `round(number, [precision])` -**描述**: 将数值四舍五入到指定的小数位数。 +### ROUND - Rounding function +**Grammar**: `round(number, [precision])` +**Description**: Rounding values to a specified decimal place. -### MOD - 取模函数 -**语法**: `mod(dividend, divisor)` -**描述**: 返回除法运算的余数。 +### MOD - Taking the modulus function +**Grammar**: `mod(dividend, divisor)` +**Description**: Returns the remainder of the division operation. -### RAND - 随机数函数 -**语法**: `rand()` -**描述**: 返回0到1之间的随机数。 +### RAND - Random Number Function +**Grammar**: `rand()` +**Description**: Returns a random number between 0 and 1. -### SIGN - 符号函数 -**语法**: `sign(number)` -**描述**: 返回数值的符号(-1、0或1)。 +### SIGN - Symbolic function +**Grammar**: `sign(number)` +**Description**: Returns the symbol for a value (-1, 0, or 1). -### 三角函数 +### Trigonometric Functions -#### SIN - 正弦函数 -**语法**: `sin(number)` -**描述**: 返回角度的正弦值(弧度制)。 +#### SIN - Sine function +**Grammar**: `sin(number)` +**Description**: Returns the sine of an angle in radians. -#### COS - 余弦函数 -**语法**: `cos(number)` -**描述**: 返回角度的余弦值(弧度制)。 +#### COS - Cosine function +**Grammar**: `cos(number)` +**Description**: Returns the cosine of an angle in radians. -#### TAN - 正切函数 -**语法**: `tan(number)` -**描述**: 返回角度的正切值(弧度制)。 +#### TAN - Tangent function +**Grammar**: `tan(number)` +**Description**: Returns the tangent of an angle in radians. -#### ASIN - 反正弦函数 -**语法**: `asin(number)` -**描述**: 返回数值的反正弦值(弧度制)。 +#### ASIN - Arcsine function +**Grammar**: `asin(number)` +**Description**: Returns the arcsine value of the value (radian system). -#### ACOS - 反余弦函数 -**语法**: `acos(number)` -**描述**: 返回数值的反余弦值(弧度制)。 +#### ACOS - Inverse cosine function +**Grammar**: `acos(number)` +**Description**: Returns the arccosine value of the value (radian system). -#### ATAN - 反正切函数 -**语法**: `atan(number)` -**描述**: 返回数值的反正切值(弧度制)。 +#### ATAN - arctangent function +**Grammar**: `atan(number)` +**Description**: Returns the arctangent value of the value (in radians). -#### ATAN2 - 双参数反正切函数 -**语法**: `atan2(y, x)` -**描述**: 返回y/x的反正切值(弧度制)。 +#### ATAN2 - Two-parameter arctangent function +**Grammar**: `atan2(y, x)` +**Description**: Returns the arctangent of y/x in radians. -### 双曲函数 +### Hyperbolic Functions -#### SINH - 双曲正弦函数 -**语法**: `sinh(number)` -**描述**: 返回数值的双曲正弦值。 +#### SINH - Hyperbolic Sine Function +**Grammar**: `sinh(number)` +**Description**: Returns the hyperbolic sine value of the value. -#### COSH - 双曲余弦函数 -**语法**: `cosh(number)` -**描述**: 返回数值的双曲余弦值。 +#### COSH - Hyperbolic cosine function +**Grammar**: `cosh(number)` +**Description**: Returns the hyperbolic cosine value of the value. -#### TANH - 双曲正切函数 -**语法**: `tanh(number)` -**描述**: 返回数值的双曲正切值。 +#### TANH - Hyperbolic tangent function +**Grammar**: `tanh(number)` +**Description**: Returns the hyperbolic tangent value of the value. -### 对数和指数函数 +### Logarithmic and exponential functions -#### EXP - 指数函数 -**语法**: `exp(number)` -**描述**: 返回e的指定次幂。 +#### EXP - Exponential Function +**Grammar**: `exp(number)` +**Description**: Returns e raised to the specified power. -#### LN - 自然对数函数 -**语法**: `ln(number)` -**描述**: 返回数值的自然对数。 +#### LN - Natural logarithmic function +**Grammar**: `ln(number)` +**Description**: Returns the natural logarithm of the value. -#### LOG - 对数函数 -**语法**: `log(base, number)` -**描述**: 返回指定底数的对数。 +#### LOG - Logarithmic function +**Grammar**: `log(base, number)` +**Description**: Returns the logarithm of the specified base. -#### LOG10 - 常用对数函数 -**语法**: `log10(number)` -**描述**: 返回数值的常用对数(以10为底)。 +#### LOG10 - Common logarithmic functions +**Grammar**: `log10(number)` +**Description**: Returns the commonly used logarithm of the value (base 10). -#### LOG2 - 二进制对数函数 -**语法**: `log2(number)` -**描述**: 返回数值的二进制对数(以2为底)。 +#### LOG2 - Binary logarithmic function +**Grammar**: `log2(number)` +**Description**: Returns the binary logarithm of the value (base 2). -### 位运算函数 +### Bitwise Arithmetic Function -#### BIT_AND - 位与函数 -**语法**: `bit_and(number1, number2)` -**描述**: 对两个整数执行位与运算。 +#### BIT_AND - Bits and functions +**Grammar**: `bit_and(number1, number2)` +**Description**: Perform bits and operations on two integers. -#### BIT_OR - 位或函数 -**语法**: `bit_or(number1, number2)` -**描述**: 对两个整数执行位或运算。 +#### BIT_OR - Bits or functions +**Grammar**: `bit_or(number1, number2)` +**Description**: Executes bits or operations on two integers. -#### BIT_XOR - 位异或函数 -**语法**: `bit_xor(number1, number2)` -**描述**: 对两个整数执行位异或运算。 +#### BIT_XOR - Bitwise XOR function +**Grammar**: `bit_xor(number1, number2)` +**Description**: Performs bitwise OR operations on two integers. -#### BIT_NOT - 位非函数 -**语法**: `bit_not(number)` -**描述**: 对整数执行位非运算。 +#### BIT_NOT - Bits are nonfunctional +**Grammar**: `bit_not(number)` +**Description**: Performs bitwise non-operations on integers. -## 📝 字符串函数 +## 📝 String function -字符串函数用于文本处理。 +String functions are used for text processing. -### UPPER - 转大写函数 -**语法**: `upper(str)` -**描述**: 将字符串转换为大写。 +### UPPER - Uppercase Conversion Function +**Grammar**: `upper(str)` +**Description**: Converts strings to uppercase. -### LOWER - 转小写函数 -**语法**: `lower(str)` -**描述**: 将字符串转换为小写。 +### LOWER - Lowercase conversion function +**Grammar**: `lower(str)` +**Description**: Converts strings to lowercase. -### CONCAT - 字符串连接函数 -**语法**: `concat(str1, str2, ...)` -**描述**: 连接多个字符串。 +### CONCAT - String concatenation function +**Grammar**: `concat(str1, str2,.)` +**Description**: Concatenate multiple strings. -### LENGTH - 字符串长度函数 -**语法**: `length(str)` -**描述**: 返回字符串的长度。 +### LENGTH - String length function +**Grammar**: `length(str)` +**Description**: Returns the length of the string. -### SUBSTRING - 子字符串函数 -**语法**: `substring(str, start, [length])` -**描述**: 从字符串中提取子字符串。 +### SUBSTRING - Substring function +**Grammar**: `substring(str, start, [length])` +**Description**: Extracting substrings from a string. -### TRIM - 去除空格函数 -**语法**: `trim(str)` -**描述**: 去除字符串两端的空格。 +### TRIM - Remove the space function +**Grammar**: `trim(str)` +**Description**: Remove spaces at both ends of a string. -### LTRIM - 去除左侧空格函数 -**语法**: `ltrim(str)` -**描述**: 去除字符串左侧的空格。 +### LTRIM - Remove the space function on the left +**Grammar**: `ltrim(str)` +**Description**: Remove the space on the left side of the string. -### RTRIM - 去除右侧空格函数 -**语法**: `rtrim(str)` -**描述**: 去除字符串右侧的空格。 +### RTRIM - Remove the space function on the right +**Grammar**: `rtrim(str)` +**Description**: Remove the space on the right side of the string. -### FORMAT - 格式化函数 -**语法**: `format(format_str, ...)` -**描述**: 按照指定格式格式化字符串。 +### FORMAT - Formatting function +**Grammar**: `format(format_str,.)` +**Description**: Format the string according to the specified format. -### ENDSWITH - 结尾检查函数 -**语法**: `endswith(str, suffix)` -**描述**: 检查字符串是否以指定后缀结尾。 +### ENDSWITH - End-check function +**Grammar**: `endswith(str, suffix)` +**Description**: Check whether the string ends with the specified suffix. -### STARTSWITH - 开头检查函数 -**语法**: `startswith(str, prefix)` -**描述**: 检查字符串是否以指定前缀开头。 +### STARTSWITH - Opening check function +**Grammar**: `startswith(str, prefix)` +**Description**: Check whether the string starts with a specified prefix. -### INDEXOF - 查找位置函数 -**语法**: `indexof(str, substring)` -**描述**: 返回子字符串在字符串中的位置。 +### INDEXOF - Find the position function +**Grammar**: `indexof(str, substring)` +**Description**: Returns the position of the substring within the string. -### REPLACE - 替换函数 -**语法**: `replace(str, old_str, new_str)` -**描述**: 替换字符串中的指定内容。 +### REPLACE - Replace the function +**Grammar**: `replace(str, old_str, new_str)` +**Description**: Replace the specified content in the string. -### SPLIT - 分割函数 -**语法**: `split(str, delimiter)` -**描述**: 按照分隔符分割字符串。 +### SPLIT - Partition function +**Grammar**: `split(str, delimiter)` +**Description**: Split strings by separators. -### LPAD - 左填充函数 -**语法**: `lpad(str, length, pad_str)` -**描述**: 在字符串左侧填充字符到指定长度。 +### LPAD - Left padding function +**Grammar**: `lpad(str, length, pad_str)` +**Description**: Fill the left side of the string with characters to a specified length. -### RPAD - 右填充函数 -**语法**: `rpad(str, length, pad_str)` -**描述**: 在字符串右侧填充字符到指定长度。 +### RPAD - Right padding function +**Grammar**: `rpad(str, length, pad_str)` +**Description**: Fill the right side of the string with characters to a specified length. -### 正则表达式函数 +### Regular Expression Functions -#### REGEXP_MATCHES - 正则匹配函数 -**语法**: `regexp_matches(str, pattern)` -**描述**: 检查字符串是否匹配正则表达式。 +#### REGEXP_MATCHES - Regular matching function +**Grammar**: `regexp_matches(str, pattern)` +**Description**: Check if the string matches a regular expression. -#### REGEXP_REPLACE - 正则替换函数 -**语法**: `regexp_replace(str, pattern, replacement)` -**描述**: 使用正则表达式替换字符串内容。 +#### REGEXP_REPLACE - Regular replacement function +**Grammar**: `regexp_replace(str, pattern, replacement)` +**Description**: Replace string content with regular expressions. -#### REGEXP_SUBSTRING - 正则提取函数 -**语法**: `regexp_substring(str, pattern)` -**描述**: 使用正则表达式提取字符串内容。 +#### REGEXP_SUBSTRING - Regular extraction function +**Grammar**: `regexp_substring(str, pattern)` +**Description**: Use regular expressions to extract string content. -## 🔄 类型转换函数 +## 🔄 Type conversion function -类型转换函数用于数据类型转换。 +The type conversion function is used for data type conversion. -### CAST - 类型转换函数 -**语法**: `cast(value as type)` -**描述**: 将值转换为指定类型。 +### CAST - Type conversion function +**Grammar**: `cast(value as type)` +**Description**: Converts values to specified types. -### HEX2DEC - 十六进制转十进制函数 -**语法**: `hex2dec(hex_str)` -**描述**: 将十六进制字符串转换为十进制数。 +### HEX2DEC - Hexadecimal to decimal function +**Grammar**: `hex2dec(hex_str)` +**Description**: Converts hexadecimal strings to decimal numbers. -### DEC2HEX - 十进制转十六进制函数 -**语法**: `dec2hex(number)` -**描述**: 将十进制数转换为十六进制字符串。 +### DEC2HEX - Decimal to hexadecimal function +**Grammar**: `dec2hex(number)` +**Description**: Converts decimal numbers into hexadecimal strings. -### ENCODE - 编码函数 -**语法**: `encode(str, encoding)` -**描述**: 按照指定编码方式编码字符串。 +### ENCODE - Encoding function +**Grammar**: `encode(str, encoding)` +**Description**: Encodes strings according to the specified encoding method. -### DECODE - 解码函数 -**语法**: `decode(str, encoding)` -**描述**: 按照指定编码方式解码字符串。 +### DECODE - Decode function +**Grammar**: `decode(str, encoding)` +**Description**: Decode the string according to the specified encoding method. -### CONVERT_TZ - 时区转换函数 -**语法**: `convert_tz(datetime, from_tz, to_tz)` -**描述**: 将日期时间从一个时区转换到另一个时区。 +### CONVERT_TZ - Time zone conversion function +**Grammar**: `convert_tz(datetime, from_tz, to_tz)` +**Description**: Convert date and time from one time zone to another. -### TO_SECONDS - 转换为秒函数 -**语法**: `to_seconds(datetime)` -**描述**: 将日期时间转换为秒数。 +### TO_SECONDS - Convert to seconds function +**Grammar**: `to_seconds(datetime)` +**Description**: Convert date and time into seconds. -### CHR - 字符函数 -**语法**: `chr(number)` -**描述**: 将ASCII码转换为字符。 +### CHR - Character function +**Grammar**: `chr(number)` +**Description**: Convert ASCII codes to characters. -### TRUNC - 截断函数 -**语法**: `trunc(number, [precision])` -**描述**: 截断数值到指定精度。 +### TRUNC - Truncation function +**Grammar**: `trunc(number, [precision])` +**Description**: Truncate values to specified precision. -### URL_ENCODE - URL编码函数 -**语法**: `url_encode(str)` -**描述**: 对字符串进行URL编码。 +### URL_ENCODE - URL Encoding Function +**Grammar**: `url_encode(str)` +**Description**: Perform URL encoding on strings. -### URL_DECODE - URL解码函数 -**语法**: `url_decode(str)` -**描述**: 对字符串进行URL解码。 +### URL_DECODE - URL Decode function +**Grammar**: `url_decode(str)` +**Description**: Perform URL decoding of strings. -## ⏰ 时间日期函数 +## ⏰ Time and date function -时间日期函数用于处理时间和日期数据。 +The time-date function is used to process time and date data. -### NOW - 当前时间函数 -**语法**: `now()` -**描述**: 返回当前的日期和时间。 +### NOW - Current time function +**Grammar**: `now()` +**Description**: Returns the current date and time. -### CURRENT_TIME - 当前时间函数 -**语法**: `current_time()` -**描述**: 返回当前时间。 +### CURRENT_TIME - Current time function +**Grammar**: `current_time()` +**Description**: Returns the current time. -### CURRENT_DATE - 当前日期函数 -**语法**: `current_date()` -**描述**: 返回当前日期。 +### CURRENT_DATE - Current date function +**Grammar**: `current_date()` +**Description**: Returns the current date. -## 🔗 JSON函数 +## 🔗 JSON Function -JSON函数用于处理JSON数据。 +JSON functions are used to handle JSON data. -### TO_JSON - 转换为JSON函数 -**语法**: `to_json(value)` -**描述**: 将值转换为JSON字符串。 +### TO_JSON - Convert to JSON function +**Grammar**: `to_json(value)` +**Description**: Converts values into JSON strings. -### FROM_JSON - 从JSON解析函数 -**语法**: `from_json(json_str)` -**描述**: 从JSON字符串解析值。 +### FROM_JSON - Parsing functions from JSON +**Grammar**: `from_json(json_str)` +**Description**: Parsing values from JSON strings. -### JSON_EXTRACT - JSON提取函数 -**语法**: `json_extract(json_source, path)` -**描述**: 从JSON字符串、Map或Array中提取指定路径的值。支持嵌套对象和数组索引。 +### JSON_EXTRACT - JSON Extraction Function +**Grammar**: `json_extract(json_source, path)` +**Description**: Extract the value of a specified path from a JSON string, Map, or Array. Supports nested objects and array indexes. -**参数**: -- `json_source`: 输入数据,可以是JSON格式字符串,也可以是Map或Array类型对象 -- `path`: 提取路径,支持 `.` 访问字段,`[]` 访问数组索引或Map Key +**Parameters**: +- `json_source`: Input data, which can be a JSON format string or an object of type Map or Array +- `path`: Extracts paths, supports `.` access to fields, `[]` access to array indexes or Map Key -**示例**: +**Example**: ```sql --- 提取基本字段 -json_extract('{"name": "Alice"}', 'name') -- 返回 "Alice" -json_extract('{"name": "Alice"}', '$.name') -- 返回 "Alice" +-- Extract the basic field +json_extract('{"name": "Alice"}', 'name') -- Back to "Alice" +json_extract('{"name": "Alice"}', '$.name') -- Back to "Alice" --- 提取嵌套字段 -json_extract('{"user": {"address": {"city": "New York"}}}', 'user.address.city') -- 返回 "New York" -json_extract('{"user": {"address": {"city": "New York"}}}', '$.user.address.city') -- 返回 "New York" +-- Extract nested fields +json_extract('{"user": {"address": {"city": "New York"}}}', 'user.address.city') -- Back to "New York" +json_extract('{"user": {"address": {"city": "New York"}}}', '$.user.address.city') -- Back to "New York" --- 提取数组元素 -json_extract('[10, 20, 30]', '[1]') -- 返回 20 -json_extract('[10, 20, 30]', '$[1]') -- 返回 20 +-- Extract array elements +json_extract('[10, 20, 30]', '[1]') -- Back to 20 +json_extract('[10, 20, 30]', '$[1]') -- Back to 20 --- 复杂嵌套提取 -json_extract('{"users": [{"name": "Alice"}, {"name": "Bob"}]}', 'users[1].name') -- 返回 "Bob" +-- Complex nested extraction +json_extract('{"users": [{"name": "Alice"}, {"name": "Bob"}]}', 'users[1].name') -- Back to "Bob" ``` -### JSON_VALID - JSON验证函数 -**语法**: `json_valid(json_str)` -**描述**: 验证字符串是否为有效的JSON。 +### JSON_VALID - JSON Validation Function +**Grammar**: `json_valid(json_str)` +**Description**: Verify whether the string is a valid JSON. -### JSON_TYPE - JSON类型函数 -**语法**: `json_type(json_str)` -**描述**: 返回JSON值的类型。 +### JSON_TYPE - JSON type function +**Grammar**: `json_type(json_str)` +**Description**: Returns the type of JSON value. -### JSON_LENGTH - JSON长度函数 -**语法**: `json_length(json_str)` -**描述**: 返回JSON数组或对象的长度。 +### JSON_LENGTH - JSON length function +**Grammar**: `json_length(json_str)` +**Description**: Returns the length of a JSON array or object. -## 🔐 哈希函数 +## 🔐 Hash function -哈希函数用于生成数据的哈希值。 +Hash functions are used to generate the hash value of data. -### MD5 - MD5哈希函数 -**语法**: `md5(str)` -**描述**: 生成字符串的MD5哈希值。 +### MD5 - MD5 Hash function +**Grammar**: `md5(str)` +**Description**: Generates the MD5 hash of a string. -### SHA1 - SHA1哈希函数 -**语法**: `sha1(str)` -**描述**: 生成字符串的SHA1哈希值。 +### SHA1 - SHA1 Hash function +**Grammar**: `sha1(str)` +**Description**: Generates the SHA-1 hash of a string. -### SHA256 - SHA256哈希函数 -**语法**: `sha256(str)` -**描述**: 生成字符串的SHA256哈希值。 +### SHA256 - SHA256 Hash function +**Grammar**: `sha256(str)` +**Description**: Generates the SHA-256 hash of a string. -### SHA512 - SHA512哈希函数 -**语法**: `sha512(str)` -**描述**: 生成字符串的SHA512哈希值。 +### SHA512 - SHA512 Hash function +**Grammar**: `sha512(str)` +**Description**: Generates the SHA-512 hash of a string. -## 📋 数组函数 +## 📋 Array functions -数组函数用于处理数组数据。 +Array functions are used to process array data. -### ARRAY_LENGTH - 数组长度函数 -**语法**: `array_length(array)` -**描述**: 返回数组的长度。 +### ARRAY_LENGTH - Array length function +**Grammar**: `array_length(array)` +**Description**: Returns the length of the array. -### ARRAY_CONTAINS - 数组包含函数 -**语法**: `array_contains(array, value)` -**描述**: 检查数组是否包含指定值。 +### ARRAY_CONTAINS - Array inclusion function +**Grammar**: `array_contains(array, value)` +**Description**: Check whether the array contains specified values. -### ARRAY_POSITION - 数组位置函数 -**语法**: `array_position(array, value)` -**描述**: 返回值在数组中的位置。 +### ARRAY_POSITION - Array position function +**Grammar**: `array_position(array, value)` +**Description**: The position of the returned value in the array. -### ARRAY_REMOVE - 数组移除函数 -**语法**: `array_remove(array, value)` -**描述**: 从数组中移除指定值。 +### ARRAY_REMOVE - Array removal function +**Grammar**: `array_remove(array, value)` +**Description**: Remove specified values from an array. -### ARRAY_DISTINCT - 数组去重函数 -**语法**: `array_distinct(array)` -**描述**: 返回数组的去重结果。 +### ARRAY_DISTINCT - Array deduplication function +**Grammar**: `array_distinct(array)` +**Description**: Returns the deduplication result of the array. -### ARRAY_INTERSECT - 数组交集函数 -**语法**: `array_intersect(array1, array2)` -**描述**: 返回两个数组的交集。 +### ARRAY_INTERSECT - Array Intersection Function +**Grammar**: `array_intersect(array1, array2)` +**Description**: Returns the intersection of two arrays. -### ARRAY_UNION - 数组并集函数 -**语法**: `array_union(array1, array2)` -**描述**: 返回两个数组的并集。 +### ARRAY_UNION - Array union function +**Grammar**: `array_union(array1, array2)` +**Description**: Returns the union of two arrays. -### ARRAY_EXCEPT - 数组差集函数 -**语法**: `array_except(array1, array2)` -**描述**: 返回两个数组的差集。 +### ARRAY_EXCEPT - Array difference set function +**Grammar**: `array_except(array1, array2)` +**Description**: Returns the difference between two arrays. -## 🔍 类型检查函数 +## 🔍 Type Check Function -类型检查函数用于检查数据类型。 +The type check function is used to check data types. -### IS_NULL - 空值检查函数 -**语法**: `is_null(value)` -**描述**: 检查值是否为NULL。 +### IS_NULL - Null check function +**Grammar**: `is_null(value)` +**Description**: Check if the value is NULL. -### IS_NOT_NULL - 非空值检查函数 -**语法**: `is_not_null(value)` -**描述**: 检查值是否不为NULL。 +### IS_NOT_NULL - Non-null check function +**Grammar**: `is_not_null(value)` +**Description**: Check if the value is not NULL. -### IS_NUMERIC - 数值检查函数 -**语法**: `is_numeric(value)` -**描述**: 检查值是否为数值类型。 +### IS_NUMERIC - Numeric Check Function +**Grammar**: `is_numeric(value)` +**Description**: Check whether the value is of the numeric type. -### IS_STRING - 字符串检查函数 -**语法**: `is_string(value)` -**描述**: 检查值是否为字符串类型。 +### IS_STRING - String Check Function +**Grammar**: `is_string(value)` +**Description**: Check whether the value is of the string type. -### IS_BOOL - 布尔值检查函数 -**语法**: `is_bool(value)` -**描述**: 检查值是否为布尔类型。 +### IS_BOOL - Boolean check function +**Grammar**: `is_bool(value)` +**Description**: Check whether the value is of boolean type. -### IS_ARRAY - 数组检查函数 -**语法**: `is_array(value)` -**描述**: 检查值是否为数组类型。 +### IS_ARRAY - Array check function +**Grammar**: `is_array(value)` +**Description**: Check whether the value is of array type. -### IS_OBJECT - 对象检查函数 -**语法**: `is_object(value)` -**描述**: 检查值是否为对象类型。 +### IS_OBJECT - Object Checking Function +**Grammar**: `is_object(value)` +**Description**: Check whether the value is of the object type. -## ❓ 条件函数 +## ❓ Conditional Function -条件函数用于条件判断和值选择。 +Conditional functions are used for conditional judgment and value selection. -### IF_NULL - 空值处理函数 -**语法**: `if_null(value, default_value)` -**描述**: 如果值为NULL,返回默认值,否则返回原值。 +### IF_NULL - Null Value Handler Function +**Grammar**: `if_null(value, default_value)` +**Description**: If the value is NULL, returns the default value; otherwise, returns the original value. -### COALESCE - 合并函数 -**语法**: `coalesce(value1, value2, ...)` -**描述**: 返回第一个非NULL值。 +### COALESCE - Merge function +**Grammar**: `coalesce(value1, value2,.)` +**Description**: Returns the first non-NULL value. -### NULL_IF - 空值转换函数 -**语法**: `null_if(value1, value2)` -**描述**: 如果两个值相等,返回NULL,否则返回第一个值。 +### NULL_IF - Null Value Conversion Function +**Grammar**: `null_if(value1, value2)` +**Description**: If two values are equal, return NULL; otherwise, return the first value. -### GREATEST - 最大值函数 -**语法**: `greatest(value1, value2, ...)` -**描述**: 返回参数中的最大值。 +### GREATEST - Maximum value function +**Grammar**: `greatest(value1, value2,.)` +**Description**: Returns the maximum value in the parameters. -### LEAST - 最小值函数 -**语法**: `least(value1, value2, ...)` -**描述**: 返回参数中的最小值。 +### LEAST - Minimum value function +**Grammar**: `least(value1, value2,.)` +**Description**: Returns the minimum value in the argument. -### CASE_WHEN - 条件选择函数 -**语法**: `case_when(condition, value_if_true, value_if_false)` -**描述**: 根据条件返回不同的值。 +### CASE_WHEN - Conditional Selection Function +**Grammar**: `case_when(condition, value_if_true, value_if_false)` +**Description**: Returns different values based on conditions. -## 📊 多行函数 +## 📊 Multiline Functions -多行函数用于处理多行数据。 +Multi-line functions are used to process multi-line data. -### UNNEST - 展开函数 -**语法**: `unnest(array)` -**描述**: 将数组展开为多行。 +### UNNEST - Expansion Function +**Grammar**: `unnest(array)` +**Description**: Expand the array into multiple rows. -## 🪟 扩展窗口函数 +## 🪟 Extend window function -扩展窗口函数提供更多窗口相关功能。 +The extended window function provides more window-related features. -### ROW_NUMBER - 行号函数 -**语法**: `row_number() OVER (ORDER BY col)` -**描述**: 为结果集中的每一行分配一个唯一的行号。 -**增量计算**: ✅ 支持 +### ROW_NUMBER - Line number function +**Grammar**: `row_number() OVER (ORDER BY col)` +**Description**: Assign a unique row number to each row in the result set. +**Incremental Calculation**: ✅ Supported -### FIRST_VALUE - 首值函数 -**语法**: `first_value(col) OVER (ORDER BY col)` -**描述**: 返回窗口中第一行的值。 -**增量计算**: ✅ 支持 +### FIRST_VALUE - Head-value function +**Grammar**: `first_value(col) OVER (ORDER BY col)` +**Description**: Returns the value of the first row in the window. +**Incremental Calculation**: ✅ Supported -### LEAD - 前导函数 -**语法**: `lead(col, offset, default_value) OVER (ORDER BY col)` -**描述**: 返回当前行之后第N行的值。 -**增量计算**: ✅ 支持 +### LEAD - Preceding Function +**Grammar**: `lead(col, offset, default_value) OVER (ORDER BY col)` +**Description**: Returns the value of the N line after the current line. +**Incremental Calculation**: ✅ Supported -### NTH_VALUE - 第N个值函数 -**语法**: `nth_value(col, n) OVER (ORDER BY col)` -**描述**: 返回窗口中第N行的值。 -**增量计算**: ✅ 支持 +### NTH_VALUE - The N th valued function +**Grammar**: `nth_value(col, n) OVER (ORDER BY col)` +**Description**: Returns the value from row N in the window. +**Incremental Calculation**: ✅ Supported -## 🔧 表达式函数 +## 🔧 Expression Function -表达式函数用于动态表达式计算。 +Expression functions are used for dynamic expression calculations. -### EXPRESSION - 表达式函数 -**语法**: `expression(expr_str)` -**描述**: 动态计算表达式字符串。 +### EXPRESSION - Expression function +**Grammar**: `expression(expr_str)` +**Description**: Dynamically computes an expression string. -### EXPR - 表达式简写函数 -**语法**: `expr(expr_str)` -**描述**: expression函数的简写形式。 +### EXPR - Expression shorthand function +**Grammar**: `expr(expr_str)` +**Describe the abbreviated form of the**:expression function. -## ⚡ 增量计算性能优势 +## ⚡ Advantages in incremental computing performance -支持增量计算的函数具有以下性能优势: +Functions supporting incremental computing have the following performance advantages: -### 内存效率 -- **传统批量计算**: 需要存储窗口内所有数据,内存使用 O(n) -- **增量计算**: 只存储必要的状态信息,内存使用 O(1) 或 O(log n) +### Memory Efficiency +- **Traditional batch computing**: Needs to store all data within the window, memory usage O(n) +- **Incremental Computation**: Only stores necessary state information, memory uses O(1) or O(log n) -### 计算效率 -- **传统批量计算**: 每次窗口触发都重新计算所有数据,时间复杂度 O(n) -- **增量计算**: 只处理新增数据,时间复杂度 O(1) +### Computational Efficiency +- **Traditional batch computing**: All data is recalculated each time a window is triggered, with time complexity O(n) +- **Incremental Calculation**: Only processes newly added data, time complexity O(1) -### 实时性 -- **传统批量计算**: 只能在窗口结束时输出结果 -- **增量计算**: 可以实时输出中间结果 +### Real-time capability +- **Traditional batch computing**: Only outputs results at the end of the window +- **Incremental Calculation**: Can output intermediate results in real time -## 🔧 自定义函数扩展 +## 🔧 Custom Function Extensions -StreamSQL 支持自定义函数扩展,详见 `functions/custom_example.go` 中的示例。可以实现: -- 自定义聚合函数(支持增量计算) -- 自定义分析函数(支持状态管理) -- 自定义数学函数 -- 自定义字符串函数 +StreamSQL Supports custom function extensions; see examples in `functions/custom_example.go` for details. It can be achieved: +- Custom aggregation functions (supports incremental calculations) +- Custom analysis functions (supports state management) +- Custom mathematical functions +- Custom string function -通过实现相应的接口,自定义函数可以无缝集成到 StreamSQL 的函数体系中。 \ No newline at end of file +By implementing the corresponding interfaces, custom functions can be seamlessly integrated into StreamSQL's function system. diff --git a/docs/FUNCTION_INTEGRATION.md b/docs/FUNCTION_INTEGRATION.md index 0e6764e..33d20c8 100644 --- a/docs/FUNCTION_INTEGRATION.md +++ b/docs/FUNCTION_INTEGRATION.md @@ -1,364 +1,364 @@ -# StreamSQL 函数系统整合指南 - -本文档说明 StreamSQL 如何整合自定义函数系统,以提供更强大和丰富的表达式计算能力,包括强大的 CASE 条件表达式支持。 - -## 🏗️ 架构概述 - -### 双引擎架构 -StreamSQL 现在支持两套表达式引擎: - -1. **自定义 expr 引擎** (`expr/expression.go`) - - 专门针对数值计算优化 - - 支持基本数学运算和函数 - - 轻量级,高性能 - -2. **expr-lang/expr 引擎** - - 功能强大的通用表达式语言 - - 支持复杂数据类型(数组、对象、字符串等) - - 丰富的内置函数库 - -### 桥接系统 -`functions/expr_bridge.go` 提供了统一的接口,自动选择最合适的引擎并整合两套函数系统。 - -### 条件表达式系统 -StreamSQL 内置了强大的 CASE 表达式支持,能够智能选择表达式引擎: -- **简单条件** → 自定义 expr 引擎(高性能) -- **复杂嵌套** → expr-lang/expr 引擎(功能完整) - -## 📚 可用函数 - -### StreamSQL 内置函数 - -#### 数学函数 (TypeMath) -| 函数 | 描述 | 示例 | -|---------------|--------|------------------------| -| `abs(x)` | 绝对值 | `abs(-5)` → `5` | -| `sqrt(x)` | 平方根 | `sqrt(16)` → `4` | -| `acos(x)` | 反余弦 | `acos(0.5)` → `1.047` | -| `asin(x)` | 反正弦 | `asin(0.5)` → `0.524` | -| `atan(x)` | 反正切 | `atan(1)` → `0.785` | -| `atan2(y,x)` | 双参数反正切 | `atan2(1,1)` → `0.785` | -| `bitand(a,b)` | 按位与 | `bitand(5,3)` → `1` | -| `bitor(a,b)` | 按位或 | `bitor(5,3)` → `7` | -| `bitxor(a,b)` | 按位异或 | `bitxor(5,3)` → `6` | -| `bitnot(x)` | 按位非 | `bitnot(5)` → `-6` | -| `ceiling(x)` | 向上取整 | `ceiling(3.2)` → `4` | -| `cos(x)` | 余弦 | `cos(0)` → `1` | -| `cosh(x)` | 双曲余弦 | `cosh(0)` → `1` | -| `exp(x)` | e的x次幂 | `exp(1)` → `2.718` | -| `floor(x)` | 向下取整 | `floor(3.8)` → `3` | -| `ln(x)` | 自然对数 | `ln(2.718)` → `1` | -| `power(x,y)` | x的y次幂 | `power(2,3)` → `8` | - -#### 字符串函数 (TypeString) -| 函数 | 描述 | 示例 | -|---------------------|-------|-------------------------------------------------| -| `concat(s1,s2,...)` | 字符串连接 | `concat("hello"," ","world")` → `"hello world"` | -| `length(s)` | 字符串长度 | `length("hello")` → `5` | -| `upper(s)` | 转大写 | `upper("hello")` → `"HELLO"` | -| `lower(s)` | 转小写 | `lower("HELLO")` → `"hello"` | - -#### 转换函数 (TypeConversion) -| 函数 | 描述 | 示例 | -|------------------------|----------|--------------------------------------------| -| `cast(value, type)` | 类型转换 | `cast("123", "int64")` → `123` | -| `hex2dec(hex)` | 十六进制转十进制 | `hex2dec("ff")` → `255` | -| `dec2hex(num)` | 十进制转十六进制 | `dec2hex(255)` → `"ff"` | -| `encode(data, format)` | 编码 | `encode("hello", "base64")` → `"aGVsbG8="` | -| `decode(data, format)` | 解码 | `decode("aGVsbG8=", "base64")` → `"hello"` | - -#### 时间日期函数 (TypeDateTime) -| 函数 | 描述 | 示例 | -|------------------|------------------|-----------------------------------| -| `now()` | 当前时间戳 | `now()` → `1640995200` | -| `current_time()` | 当前时间(HH:MM:SS) | `current_time()` → `"14:30:25"` | -| `current_date()` | 当前日期(YYYY-MM-DD) | `current_date()` → `"2025-01-01"` | - -#### 聚合函数 (TypeAggregation) -| 函数 | 描述 | 示例 | -|---------------|-----|---------------------------| -| `sum(...)` | 求和 | `sum(1,2,3)` → `6` | -| `avg(...)` | 平均值 | `avg(1,2,3)` → `2` | -| `min(...)` | 最小值 | `min(1,2,3)` → `1` | -| `max(...)` | 最大值 | `max(1,2,3)` → `3` | -| `count(...)` | 计数 | `count(1,2,3)` → `3` | -| `stddev(...)` | 标准差 | `stddev(1,2,3)` → `0.816` | -| `median(...)` | 中位数 | `median(1,2,3)` → `2` | - -### expr-lang/expr 内置函数 - -#### 数学函数 -| 函数 | 描述 | 示例 | -|------------|------|--------------------| -| `abs(x)` | 绝对值 | `abs(-5)` → `5` | -| `ceil(x)` | 向上取整 | `ceil(3.2)` → `4` | -| `floor(x)` | 向下取整 | `floor(3.8)` → `3` | -| `round(x)` | 四舍五入 | `round(3.6)` → `4` | -| `max(a,b)` | 最大值 | `max(5,3)` → `5` | -| `min(a,b)` | 最小值 | `min(5,3)` → `3` | - -#### 字符串函数 -| 函数 | 描述 | 示例 | -|------------------------|--------|------------------------------------------| -| `trim(s)` | 去除首尾空格 | `trim(" hello ")` → `"hello"` | -| `upper(s)` | 转大写 | `upper("hello")` → `"HELLO"` | -| `lower(s)` | 转小写 | `lower("HELLO")` → `"hello"` | -| `split(s, delimiter)` | 分割字符串 | `split("a,b,c", ",")` → `["a","b","c"]` | -| `replace(s, old, new)` | 替换字符串 | `replace("hello", "l", "x")` → `"hexxo"` | -| `indexOf(s, sub)` | 查找子串位置 | `indexOf("hello", "ll")` → `2` | -| `hasPrefix(s, prefix)` | 检查前缀 | `hasPrefix("hello", "he")` → `true` | -| `hasSuffix(s, suffix)` | 检查后缀 | `hasSuffix("hello", "lo")` → `true` | - -#### 数组/集合函数 -| 函数 | 描述 | 示例 | -|----------------------------|-----------|----------------------------------------| -| `all(array, predicate)` | 所有元素满足条件 | `all([2,4,6], # % 2 == 0)` → `true` | -| `any(array, predicate)` | 任一元素满足条件 | `any([1,3,4], # % 2 == 0)` → `true` | -| `filter(array, predicate)` | 过滤元素 | `filter([1,2,3,4], # > 2)` → `[3,4]` | -| `map(array, expression)` | 转换元素 | `map([1,2,3], # * 2)` → `[2,4,6]` | -| `find(array, predicate)` | 查找元素 | `find([1,2,3], # > 2)` → `3` | -| `count(array, predicate)` | 计数满足条件的元素 | `count([1,2,3,4], # > 2)` → `2` | -| `concat(array1, array2)` | 连接数组 | `concat([1,2], [3,4])` → `[1,2,3,4]` | -| `flatten(array)` | 展平数组 | `flatten([[1,2],[3,4]])` → `[1,2,3,4]` | -| `len(value)` | 获取长度 | `len([1,2,3])` → `3` | - -#### 时间函数 -| 函数 | 描述 | 示例 | -|---------------|-------|-------------------------------| -| `now()` | 当前时间 | `now()` → `时间对象` | -| `duration(s)` | 解析时间段 | `duration("1h30m")` → `时间段对象` | -| `date(s)` | 解析日期 | `date("2023-12-01")` → `日期对象` | - -#### 类型转换函数 -| 函数 | 描述 | 示例 | -|------|------|------| -| `int(x)` | 转整数 | `int("123")` → `123` | -| `float(x)` | 转浮点数 | `float("123.45")` → `123.45` | -| `string(x)` | 转字符串 | `string(123)` → `"123"` | -| `type(x)` | 获取类型 | `type(123)` → `"int"` | - -#### JSON/编码函数 -| 函数 | 描述 | 示例 | -|-----------------|----------|--------------------------------------| -| `toJSON(x)` | 转JSON | `toJSON({"a":1})` → `'{"a":1}'` | -| `fromJSON(s)` | 解析JSON | `fromJSON('{"a":1}')` → `{"a":1}` | -| `toBase64(s)` | Base64编码 | `toBase64("hello")` → `"aGVsbG8="` | -| `fromBase64(s)` | Base64解码 | `fromBase64("aGVsbG8=")` → `"hello"` | - -## 🎯 条件表达式 - -### CASE表达式 - -StreamSQL 支持强大的 CASE 条件表达式,用于实现复杂的条件逻辑判断。 - -#### 语法支持 - -**搜索CASE表达式**: -```sql -CASE - WHEN condition1 THEN result1 +# StreamSQL Functional System Integration Guide + +This document explains how StreamSQL integrates custom function systems to provide more powerful and rich expression computing capabilities, including robust CASE conditional expression support. + +## 🏗️ Architecture Overview + +### Dual-Engine Architecture +StreamSQL Now supports two sets of expression engines: + +1. **Custom expr Engine** (`expr/expression.go`) + - Optimized specifically for numerical calculations + - Supports basic mathematical operations and functions + - Lightweight, high performance + +2. **expr-lang/expr Engine** + - A powerful general-purpose expression language + - Supports complex data types (arrays, objects, strings, etc.) + - Rich built-in function library + +### Bridging Systems +`functions/expr_bridge.go` provides a unified interface, automatically selects the most suitable engine, and integrates two functional systems. + +### Conditional Expression System +StreamSQL Built-in powerful CASE expression support, allowing intelligent selection of expression engines: +- **Simple conditions** → Custom expr engine (high performance) +- **Complex nested** → expr-lang/expr engine (fully featured) + +## 📚 Available functions + +### StreamSQL built-in functions + +#### Mathematical Functions (TypeMath) +| Function | Description | Example | +|---------------|--------|------------------------| +| `abs(x)` | Absolute value | `abs(-5)` → `5` | +| `sqrt(x)` | square root | `sqrt(16)` → `4` | +| `acos(x)` | Anticosine | `acos(0.5)` → `1.047` | +| `asin(x)` | Opposite sine | `asin(0.5)` → `0.524` | +| `atan(x)` | Arctangent | `atan(1)` → `0.785` | +| `atan2(y,x)` | Dual-parameter arctangent | `atan2(1,1)` → `0.785` | +| `bitand(a,b)` | By bit and | `bitand(5,3)` → `1` | +| `bitor(a,b)` | Bitwise or | `bitor(5,3)` → `7` | +| `bitxor(a,b)` | By bit or | `bitxor(5,3)` → `6` | +| `bitnot(x)` | By bit, not | `bitnot(5)` → `-6` | +| `ceiling(x)` | Round up | `ceiling(3.2)` → `4` | +| `cos(x)` | Cosine | `cos(0)` → `1` | +| `cosh(x)` | Hyperbolic cosine | `cosh(0)` → `1` | +| `exp(x)` | e x power | `exp(1)` → `2.718` | +| `floor(x)` | Round down | `floor(3.8)` → `3` | +| `ln(x)` | Natural logarithm | `ln(2.718)` → `1` | +| `power(x,y)` | x y power | `power(2,3)` → `8` | + +#### String Function (TypeString) +| Function | Description | Example | +|---------------------|-------|-------------------------------------------------| +| `concat(s1,s2,.)` | String concatenation | `concat("hello"," ","world")` → `"hello world"` | +| `length(s)` | String length | `length("hello")` → `5` | +| `upper(s)` | Capitalize | `upper("hello")` → `"HELLO"` | +| `lower(s)` | Lowercase | `lower("HELLO")` → `"hello"` | + +#### Transformation Function (TypeConversion) +| Function | Description | Example | +|------------------------|----------|--------------------------------------------| +| `cast(value, type)` | Type conversion | `cast("123", "int64")` → `123` | +| `hex2dec(hex)` | Hexadecimal to decimal | `hex2dec("ff")` → `255` | +| `dec2hex(num)` | Decimal to hexadecimal | `dec2hex(255)` → `"ff"` | +| `encode(data, format)` | Code: | `encode("hello", "base64")` → `"aGVsbG8="` | +| `decode(data, format)` | Decoding | `decode("aGVsbG8=", "base64")` → `"hello"` | + +#### Time-Date Function (TypeDateTime) +| Function | Description | Example | +|------------------|------------------|-----------------------------------| +| `now()` | Current timestamp | `now()` → `1640995200` | +| `current_time()` | Current time (HH:MM:SS) | `current_time()` → `"14:30:25"` | +| `current_date()` | Current date (YYYY-MM-DD) | `current_date()` → `"2025-01-01"` | + +#### Aggregate Function (TypeAggregation) +| Function | Description | Example | +|---------------|-----|---------------------------| +| `sum(.)` | Seeking peace | `sum(1,2,3)` → `6` | +| `avg(.)` | Average value | `avg(1,2,3)` → `2` | +| `min(.)` | Minimum value | `min(1,2,3)` → `1` | +| `max(.)` | Maximum | `max(1,2,3)` → `3` | +| `count(.)` | Count | `count(1,2,3)` → `3` | +| `stddev(.)` | Standard deviation | `stddev(1,2,3)` → `0.816` | +| `median(.)` | Median | `median(1,2,3)` → `2` | + +### expr-lang/expr built-in functions + +#### Mathematical Functions +| Function | Description | Example | +|------------|------|--------------------| +| `abs(x)` | Absolute value | `abs(-5)` → `5` | +| `ceil(x)` | Round up | `ceil(3.2)` → `4` | +| `floor(x)` | Round down | `floor(3.8)` → `3` | +| `round(x)` | Rounding | `round(3.6)` → `4` | +| `max(a,b)` | Maximum | `max(5,3)` → `5` | +| `min(a,b)` | Minimum value | `min(5,3)` → `3` | + +#### String function +| Function | Description | Example | +|------------------------|--------|------------------------------------------| +| `trim(s)` | Remove the starting and ending spaces | `trim("hello")` → `"hello"` | +| `upper(s)` | Capitalize | `upper("hello")` → `"HELLO"` | +| `lower(s)` | Lowercase | `lower("HELLO")` → `"hello"` | +| `split(s, delimiter)` | Split string | `split("a,b,c", ",")` → `["a","b","c"]` | +| `replace(s, old, new)` | Replace string | `replace("hello", "l", "x")` → `"hexxo"` | +| `indexOf(s, sub)` | Find the position of the substring | `indexOf("hello", "ll")` → `2` | +| `hasPrefix(s, prefix)` | Check the prefix | `hasPrefix("hello", "he")` → `true` | +| `hasSuffix(s, suffix)` | Check the suffix | `hasSuffix("hello", "lo")` → `true` | + +#### Array/Set Functions +| Function | Description | Example | +|----------------------------|-----------|----------------------------------------| +| `all(array, predicate)` | All elements satisfy the condition | `all([2,4,6], # % 2 == 0)` → `true` | +| `any(array, predicate)` | Any element satisfies the condition | `any([1,3,4], # % 2 == 0)` → `true` | +| `filter(array, predicate)` | Filter element | `filter([1,2,3,4], # > 2)` → `[3,4]` | +| `map(array, expression)` | Transform element | `map([1,2,3], # * 2)` → `[2,4,6]` | +| `find(array, predicate)` | Find element | `find([1,2,3], # > 2)` → `3` | +| `count(array, predicate)` | Count elements that meet the condition | `count([1,2,3,4], # > 2)` → `2` | +| `concat(array1, array2)` | Join array | `concat([1,2], [3,4])` → `[1,2,3,4]` | +| `flatten(array)` | Spread array | `flatten([[1,2],[3,4]])` → `[1,2,3,4]` | +| `len(value)` | Get length | `len([1,2,3])` → `3` | + +#### Time Function +| Function | Description | Example | +|---------------|-------|-------------------------------| +| `now()` | Current time | `now()` → `time object` | +| `duration(s)` | Parsing time period | `duration("1h30m")` → `duration object` | +| `date(s)` | Date of resolution | `date("2023-12-01")` → `date object` | + +#### Type conversion function +| Function | Description | Example | +|------|------|------| +| `int(x)` | Convert to integer | `int("123")` → `123` | +| `float(x)` | Floating point number | `float("123.45")` → `123.45` | +| `string(x)` | Convert to string | `string(123)` → `"123"` | +| `type(x)` | Get type | `type(123)` → `"int"` | + +#### JSON/ Encoding function +| Function | Description | Example | +|-----------------|----------|--------------------------------------| +| `toJSON(x)` | Convert JSON | `toJSON({"a":1})` → `'{"a":1}'` | +| `fromJSON(s)` | Analysis: JSON | `fromJSON('{"a":1}')` → `{"a":1}` | +| `toBase64(s)` | Base64 Encoding | `toBase64("hello")` → `"aGVsbG8="` | +| `fromBase64(s)` | Base64 Decode | `fromBase64("aGVsbG8=")` → `"hello"` | + +## 🎯 Conditional expressions + +### CASE Expression + +StreamSQL Supports powerful CASE conditional expressions for implementing complex conditional logical judgments. + +#### Syntax support + +**Search CASE Expressions**: +```sql +CASE + WHEN condition1 THEN result1 WHEN condition2 THEN result2 ... ELSE default_result -END -``` - -**简单CASE表达式**: -```sql -CASE expression - WHEN value1 THEN result1 +END +``` + +**Simple CASE Expressions**: +```sql +CASE expression + WHEN value1 THEN result1 WHEN value2 THEN result2 ... ELSE default_result -END -``` - -#### 功能特性 - -| 特性 | 支持状态 | 描述 | -|------|----------|------| -| **基本条件判断** | ✅ | 支持 WHEN/THEN/ELSE 逻辑 | -| **多重条件** | ✅ | 支持多个 WHEN 子句 | -| **逻辑运算符** | ✅ | 支持 AND、OR、NOT 操作 | -| **比较操作符** | ✅ | 支持 >、<、>=、<=、=、!= 等 | -| **数学函数** | ✅ | 支持 ABS、ROUND、CEIL 等函数调用 | -| **算术表达式** | ✅ | 支持 +、-、*、/ 运算 | -| **字符串操作** | ✅ | 支持字符串字面量和函数 | -| **聚合集成** | ✅ | 可在 SUM、AVG、COUNT 等聚合函数中使用 | -| **字段引用** | ✅ | 支持动态字段提取和计算 | -| **嵌套CASE** | ⚠️ | 部分支持(回退到 expr-lang) | - -#### 使用示例 - -**设备状态分类**: -```sql -SELECT deviceId, - CASE +END +``` + +#### Functional Features + +| Features | Support status | Description | +|------|----------|------| +| **Basic condition judgment** | ✅ | Supports WHEN/THEN/ELSE logic | +| **Multiple conditions** | ✅ | Supports multiple WHEN clauses | +| **Logical Operator** | ✅ | Supports AND, OR, NOT operations | +| **Comparison Operator** | ✅ | Supports >, <、> =, <=, =,!= and other | +| **Mathematical Functions** | ✅ | Supports ABS, ROUND, CEIL, and other functions to call | +| **Arithmetic Expression** | ✅ | Supports +、-、*, / operations | +| **String Operations** | ✅ | Supports string literals and functions | +| **Aggregate Integration** | ✅ | You can use |SUM, AVG, COUNT, and other aggregate functions +| **Field Reference** | ✅ | Supports dynamic field extraction and calculation | +| **Nested CASE** | ⚠️ | Partial support (rollback to expr-lang) | + +#### Usage examples + +**Equipment Status Classification**: +```sql +SELECT deviceId, + CASE WHEN temperature > 30 AND humidity > 70 THEN 'CRITICAL' WHEN temperature > 25 OR humidity > 80 THEN 'WARNING' ELSE 'NORMAL' END as alert_level -FROM stream -``` - -**条件聚合统计**: -```sql -SELECT deviceId, - COUNT(CASE WHEN temperature > 25 THEN 1 END) as high_temp_count, +FROM stream +``` + +**Conditional Aggregation Statistics**: +```sql +SELECT deviceId, + COUNT(CASE WHEN temperature > 25 THEN 1 END) as high_temp_count, SUM(CASE WHEN status = 'active' THEN temperature ELSE 0 END) as active_temp_sum, AVG(CASE WHEN humidity > 50 THEN humidity END) as avg_high_humidity FROM stream -GROUP BY deviceId, TumblingWindow('5s') -``` - -**数学函数和算术表达式**: -```sql -SELECT deviceId, - CASE +GROUP BY deviceId, TumblingWindow('5s') +``` + +**Mathematical Functions and Arithmetic Expressions**: +```sql +SELECT deviceId, + CASE WHEN ABS(temperature - 25) < 5 THEN 'NORMAL' WHEN temperature * 1.8 + 32 > 100 THEN 'HOT_F' WHEN ROUND(temperature) = 20 THEN 'EXACT_20' ELSE 'OTHER' END as temp_classification -FROM stream -``` - -**状态码映射**: -```sql -SELECT deviceId, - CASE status +FROM stream +``` + +**Status Code Mapping**: +```sql +SELECT deviceId, + CASE status WHEN 'active' THEN 1 WHEN 'inactive' THEN 0 WHEN 'maintenance' THEN -1 ELSE -999 END as status_code -FROM stream -``` - -#### 表达式引擎选择 - -CASE表达式的处理遵循以下规则: - -1. **简单条件** → 使用自定义 expr 引擎(高性能) -2. **嵌套CASE或复杂表达式** → 自动回退到 expr-lang/expr(功能完整) -3. **混合函数调用** → 智能选择最合适的引擎 - -#### 性能优化 - -- **条件顺序**:将最常见的条件放在前面 -- **函数调用**:避免在条件中重复调用相同函数 -- **类型一致性**:保持THEN子句返回相同类型以避免转换开销 - -## 🔧 使用方法 - -### 基本使用 - -```go -import ( +FROM stream +``` + +#### Expression Engine Selection + +CASE Expressions are handled according to the following rules: + +1. **Simple Condition** → Use a custom expr engine (high performance) +2. **Nested CASE or complex expressions** → automatically falls back to expr-lang/expr (full functionality) +3. **Hybrid function calls** → Intelligently select the most suitable engine + +#### Performance Optimization + +- **Conditional order**: Put the most common conditions first +- **Function Call**: Prevents repeated calls to the same function in conditions +- **Type consistency**: Keep THEN clauses returning the same type to avoid conversion overhead + +## 🔧 How to use + +### Basic usage + +```go +import ( "github.com/rulego/streamsql/functions" - "github.com/rulego/streamsql/utils/cast" -) - -// 直接使用桥接器评估表达式 -result, err := functions.EvaluateWithBridge("abs(-5) + len([1,2,3])", map[string]interface{}{}) -// result: 8 (5 + 3) - -// CASE表达式示例 -caseResult, err := functions.EvaluateWithBridge( - "CASE WHEN temperature > 30 THEN 'HOT' ELSE 'NORMAL' END", - map[string]interface{}{"temperature": 35.0}) -// caseResult: "HOT" -``` - -### 在 SQL 查询中使用 - -```sql --- 使用 StreamSQL 函数 -SELECT device, abs(temperature - 20) as deviation -FROM stream; - --- 使用 expr-lang 函数 -SELECT device, filter(measurements, # > 10) as high_values -FROM stream; - --- 混合使用 -SELECT device, encode(concat(device, "_", string(now())), "base64") as device_id -FROM stream; -``` - -### 表达式引擎选择 - -表达式引擎会自动选择: - -1. **简单数值表达式** → 使用自定义 expr 引擎(更快) -2. **复杂表达式或使用高级函数** → 使用 expr-lang/expr(更强大) - -### 函数冲突解决 - -当两个系统有同名函数时: - -1. **默认优先级**:expr-lang/expr > StreamSQL -2. **访问 StreamSQL 版本**:使用 `streamsql_` 前缀,如 `streamsql_abs(-5)` -3. **明确指定**:通过函数解析器手动选择 - -## 🛠️ 高级用法 - -### 获取所有可用函数 - -```go -info := functions.GetAllAvailableFunctions() + "github.com/rulego/streamsql/utils/cast" +) + +// Directly use the bridge to evaluate the expression +result, err := functions.EvaluateWithBridge("abs(-5) + len([1,2,3])", map[string]interface{}{}) +// result: 8 (5 + 3) + +// CASE example expression +caseResult, err := functions.EvaluateWithBridge( + "CASE WHEN temperature > 30 THEN 'HOT' ELSE 'NORMAL' END", + map[string]interface{}{"temperature": 35.0}) +// caseResult: "HOT" +``` + +### Used in SQL queries + +```sql +-- Use the StreamSQL function +SELECT device, abs(temperature - 20) as deviation +FROM stream; + +-- Use the expr-lang function +SELECT device, filter(measurements, # > 10) as high_values +FROM stream; + +-- Mixed use +SELECT device, encode(concat(device, "_", string(now())), "base64") as device_id +FROM stream; +``` + +### Expression Engine Selection + +The expression engine will automatically select: + +1. **Simple Numeric Expressions** → Using a Custom expr Engine (Faster) +2. **complex expressions or use advanced functions** → use expr-lang/expr (more powerful) + +### Function Conflict Resolution + +When two systems have functions with the same name: + +1. **Default priority**: expr-lang/expr > StreamSQL +2. **access StreamSQL version**: Use `streamsql_` prefixes such as `streamsql_abs(-5)` +3. **Clearly specify**: Manually select via function parser + +## 🛠️ Advanced usage + +### Get all available functions + +```go +info := functions.GetAllAvailableFunctions() streamSQLFuncs := info["streamsql"] -exprLangFuncs := info["expr-lang"] -``` - -### 自定义函数注册 - -```go -// 注册到 StreamSQL 系统 -err := functions.RegisterCustomFunction("celsius_to_fahrenheit", - functions.TypeMath, "温度转换", "摄氏度转华氏度", 1, 1, - func(ctx *functions.FunctionContext, args []interface{}) (interface{}, error) { +exprLangFuncs := info["expr-lang"] +``` + +### Custom function registration + +```go +// Register with the StreamSQL system +err := functions.RegisterCustomFunction("celsius_to_fahrenheit", + functions.TypeMath, "温度转换", "摄氏度转华氏度", 1, 1, + func(ctx *functions.FunctionContext, args []interface{}) (interface{}, error) { celsius, _ := cast.ToFloat64E(args[0]) - return celsius*1.8 + 32, nil - }) - -// 函数会自动在两个引擎中可用 -``` - -### 表达式编译和缓存 - -```go -bridge := functions.GetExprBridge() - -// 编译表达式(可缓存) -program, err := bridge.CompileExpressionWithStreamSQLFunctions( - "abs(temperature - 20) > 5", - map[string]interface{}{"temperature": 0.0}) - -// 重复执行(高性能) -result, err := expr.Run(program, map[string]interface{}{"temperature": 25.5}) -``` - -## 🔍 性能考虑 - -### 选择合适的引擎 - -1. **纯数值计算**:优先使用自定义 expr 引擎 -2. **字符串/数组操作**:使用 expr-lang/expr -3. **复杂逻辑表达式**:使用 expr-lang/expr - -### 优化建议 - -1. **预编译表达式**:对于重复使用的表达式,预编译以提高性能 -2. **函数选择**:优先使用性能更好的版本 -3. **数据类型**:避免不必要的类型转换 - -## 📝 示例 - -### 温度监控 - -```sql -SELECT + return celsius*1.8 + 32, nil + }) + +// The function will automatically be available in both engines +``` + +### Expression Compilation and Caching + +```go +bridge := functions.GetExprBridge() + +// Compiled expressions (cacheable) +program, err := bridge.CompileExpressionWithStreamSQLFunctions( + "abs(temperature - 20) > 5", + map[string]interface{}{"temperature": 0.0}) + +// Repetitive execution (high performance) +result, err := expr.Run(program, map[string]interface{}{"temperature": 25.5}) +``` + +## 🔍 Performance considerations + +### Choose the Right Engine + +1. **Pure Numerical Computation**: Prioritize using custom expr engines +2. **String/Array Operations**: Use expr-lang/expr +3. **Complex Logical Expressions**: Use expr-lang/expr + +### Optimization Suggestions + +1. **Precompiled Expressions**: For reused expressions, precompile them to improve performance +2. **Function Select**: Prioritize the version with better performance +3. **Data Type**: Avoid unnecessary type conversions + +## 📝 Example + +### Temperature monitoring + +```sql +SELECT device, temperature, abs(temperature - 20) as deviation, @@ -369,74 +369,74 @@ SELECT END as status, encode(concat(device, "_", current_date()), "base64") as device_key FROM temperature_stream -WHERE abs(temperature - 20) > 5; -``` - -### 智能告警系统 - -```sql -SELECT +WHERE abs(temperature - 20) > 5; +``` + +### Intelligent Alarm System + +```sql +SELECT device_id, timestamp, - temperature, - humidity, - pressure, - -- 多级告警判断 - CASE - WHEN temperature > 40 AND humidity > 80 THEN 'CRITICAL_HEAT_HUMID' - WHEN temperature > 35 OR humidity > 90 THEN 'WARNING_HIGH' + temperature, + humidity, + pressure, + -- Multi-level alarm detection + CASE + WHEN temperature > 40 AND humidity > 80 THEN 'CRITICAL_HEAT_HUMID' + WHEN temperature > 35 OR humidity > 90 THEN 'WARNING_HIGH' WHEN temperature < 5 AND pressure < 950 THEN 'CRITICAL_COLD_LOW_PRESSURE' - WHEN ABS(temperature - 25) < 2 AND humidity BETWEEN 40 AND 60 THEN 'OPTIMAL' - ELSE 'NORMAL' - END as alert_level, - -- 设备状态映射 - CASE device_status - WHEN 'online' THEN 1 - WHEN 'offline' THEN 0 - WHEN 'maintenance' THEN -1 - ELSE -999 - END as status_code, - -- 条件计算 - CASE - WHEN temperature > 0 THEN ROUND(temperature * 1.8 + 32, 1) - ELSE NULL + WHEN ABS(temperature - 25) < 2 AND humidity BETWEEN 40 AND 60 THEN 'OPTIMAL' + ELSE 'NORMAL' + END as alert_level, + -- Device status mapping + CASE device_status + WHEN 'online' THEN 1 + WHEN 'offline' THEN 0 + WHEN 'maintenance' THEN -1 + ELSE -999 + END as status_code, + -- Condition calculation + CASE + WHEN temperature > 0 THEN ROUND(temperature * 1.8 + 32, 1) + ELSE NULL END as fahrenheit_temp FROM sensor_stream -WHERE device_id IS NOT NULL; -``` - -### 条件聚合分析 - -```sql -SELECT - device_type, - location, - -- 条件计数 - COUNT(CASE WHEN temperature > 30 THEN 1 END) as hot_readings, - COUNT(CASE WHEN temperature < 10 THEN 1 END) as cold_readings, - COUNT(CASE WHEN humidity > 70 THEN 1 END) as humid_readings, - -- 条件求和 - SUM(CASE WHEN status = 'active' THEN power_consumption ELSE 0 END) as active_power_sum, - -- 条件平均值 - AVG(CASE WHEN temperature BETWEEN 20 AND 30 THEN temperature END) as normal_temp_avg, - -- 复杂条件统计 - COUNT(CASE - WHEN temperature > 25 AND humidity < 60 AND status = 'active' - THEN 1 +WHERE device_id IS NOT NULL; +``` + +### Conditional Aggregation Analysis + +```sql +SELECT + device_type, + location, + -- Conditional counting + COUNT(CASE WHEN temperature > 30 THEN 1 END) as hot_readings, + COUNT(CASE WHEN temperature < 10 THEN 1 END) as cold_readings, + COUNT(CASE WHEN humidity > 70 THEN 1 END) as humid_readings, + -- Conditional sum + SUM(CASE WHEN status = 'active' THEN power_consumption ELSE 0 END) as active_power_sum, + -- Conditional mean + AVG(CASE WHEN temperature BETWEEN 20 AND 30 THEN temperature END) as normal_temp_avg, + -- Statistics under complex conditions + COUNT(CASE + WHEN temperature > 25 AND humidity < 60 AND status = 'active' + THEN 1 END) as optimal_active_count FROM device_stream GROUP BY device_type, location, TumblingWindow('10m') -HAVING COUNT(*) > 100; -``` - -### 数据处理 - -```sql -SELECT +HAVING COUNT(*) > 100; +``` + +### Data Processing + +```sql +SELECT sensor_id, filter(readings, # > avg(readings)) as above_average, map(readings, round(#, 2)) as rounded_readings, len(readings) as reading_count FROM sensor_data WHERE len(readings) > 10; -``` \ No newline at end of file +``` diff --git a/docs/FUNCTION_QUICK_START.md b/docs/FUNCTION_QUICK_START.md index 326f560..d48ecbf 100644 --- a/docs/FUNCTION_QUICK_START.md +++ b/docs/FUNCTION_QUICK_START.md @@ -1,56 +1,56 @@ -# StreamSQL 自定义函数快速入门 - -## 🚀 概述 - -StreamSQL 提供了强大的自定义函数系统,让你可以轻松扩展框架功能。本指南将帮你快速上手,创建和使用自定义函数。 - -## 📋 快速开始 - -### 1. 注册简单函数 - -最简单的方式是使用 `RegisterCustomFunction` 方法: - -```go -import ( +# StreamSQL Quick Start with Custom Functions + +## 🚀 Overview + +StreamSQL offers a powerful custom function system, allowing you to easily extend the framework's functionality. This guide will help you get started quickly by creating and using custom functions. + +## 📋 Get Started Quickly + +### 1. Register simple functions + +The simplest way is to use the `RegisterCustomFunction` method: + +```go +import ( "github.com/rulego/streamsql/functions" - "github.com/rulego/streamsql/utils/cast" -) - -// 注册一个平方函数 -err := functions.RegisterCustomFunction( - "square", // 函数名 - functions.TypeMath, // 函数类型 - "数学函数", // 分类 - "计算数值的平方", // 描述 - 1, // 最少参数数量 - 1, // 最多参数数量 - func(ctx *functions.FunctionContext, args []interface{}) (interface{}, error) { - // 参数转换 - val, err := cast.ToFloat64E(args[0]) - if err != nil { - return nil, err - } - // 业务逻辑 - return val * val, nil - }, -) -``` - -### 2. 在SQL中使用 - -```sql -SELECT device, square(value) as squared_value FROM stream -``` - -## 🎯 函数类型 - -### 数学函数 (TypeMath) - -```go -// 距离计算函数 -functions.RegisterCustomFunction( - "distance", - functions.TypeMath, + "github.com/rulego/streamsql/utils/cast" +) + +// Register a square function +err := functions.RegisterCustomFunction( + "square", // Function name + functions.TypeMath, // Function type + "数学函数", // Classification + "计算数值的平方", // Description + 1, // Minimum number of parameters + 1, // Maximum number of parameters + func(ctx *functions.FunctionContext, args []interface{}) (interface{}, error) { + // Parameter conversion + val, err := cast.ToFloat64E(args[0]) + if err != nil { + return nil, err + } + // Business logic + return val * val, nil + }, +) +``` + +### 2. Used in SQL + +```sql +SELECT device, square(value) as squared_value FROM stream +``` + +## 🎯 Function type + +### Mathematical Functions (TypeMath) + +```go +// Distance calculation function +functions.RegisterCustomFunction( + "distance", + functions.TypeMath, "几何数学", "计算两点间的欧几里得距离", 4, 4, @@ -61,20 +61,20 @@ functions.RegisterCustomFunction( y2, _ := cast.ToFloat64E(args[3]) return math.Sqrt(math.Pow(x2-x1, 2) + math.Pow(y2-y1, 2)), nil - }, -) - -// SQL使用 -// SELECT device, distance(lat1, lon1, lat2, lon2) as dist FROM stream -``` - -### 字符串函数 (TypeString) - -```go -// 字符串反转函数 -functions.RegisterCustomFunction( - "reverse", - functions.TypeString, + }, +) + +// SQL use +// SELECT device, distance(lat1, lon1, lat2, lon2) as dist FROM stream +``` + +### String Function (TypeString) + +```go +// String inversion function +functions.RegisterCustomFunction( + "reverse", + functions.TypeString, "字符串处理", "反转字符串", 1, 1, @@ -90,20 +90,20 @@ functions.RegisterCustomFunction( } return string(runes), nil - }, -) - -// SQL使用 -// SELECT device, reverse(device_name) as reversed_name FROM stream -``` - -### 转换函数 (TypeConversion) - -```go -// IP地址转整数 -functions.RegisterCustomFunction( - "ip_to_int", - functions.TypeConversion, + }, +) + +// SQL use +// SELECT device, reverse(device_name) as reversed_name FROM stream +``` + +### Transformation Function (TypeConversion) + +```go +// IP address to integer +functions.RegisterCustomFunction( + "ip_to_int", + functions.TypeConversion, "网络转换", "将IPv4地址转换为32位整数", 1, 1, @@ -119,20 +119,20 @@ functions.RegisterCustomFunction( } return int64(ip[0])<<24 + int64(ip[1])<<16 + int64(ip[2])<<8 + int64(ip[3]), nil - }, -) - -// SQL使用 -// SELECT device, ip_to_int(client_ip) as ip_num FROM stream -``` - -### 时间日期函数 (TypeDateTime) - -```go -// 时间格式化函数 -functions.RegisterCustomFunction( - "format_time", - functions.TypeDateTime, + }, +) + +// SQL use +// SELECT device, ip_to_int(client_ip) as ip_num FROM stream +``` + +### Time-Date Function (TypeDateTime) + +```go +// Time formatting function +functions.RegisterCustomFunction( + "format_time", + functions.TypeDateTime, "时间格式化", "格式化Unix时间戳", 2, 2, @@ -149,29 +149,29 @@ functions.RegisterCustomFunction( t := time.Unix(timestamp, 0) return t.Format(format), nil - }, -) - -// SQL使用 -// SELECT device, format_time(timestamp, '2006-01-02 15:04:05') as formatted_time FROM stream -``` - -## 🏗️ 复杂函数实现 - -对于复杂函数,建议使用结构体方式: - -```go -// 1. 定义函数结构 -type StatefulFunction struct { - *functions.BaseFunction - counter int64 - mutex sync.Mutex -} - -// 2. 构造函数 -func NewStatefulFunction() *StatefulFunction { - return &StatefulFunction{ - BaseFunction: functions.NewBaseFunction( + }, +) + +// SQL use +// SELECT device, format_time(timestamp, '2006-01-02 15:04:05') as formatted_time FROM stream +``` + +## 🏗️ Implementation of complex functions + +For complex functions, it is recommended to use structs: + +```go +// 1. Define the function structure +type StatefulFunction struct { + *functions.BaseFunction + counter int64 + mutex sync.Mutex +} + +// 2. Constructor +func NewStatefulFunction() *StatefulFunction { + return &StatefulFunction{ + BaseFunction: functions.NewBaseFunction( "counter", functions.TypeCustom, "状态函数", @@ -179,58 +179,58 @@ func NewStatefulFunction() *StatefulFunction { 0, 0, ), counter: 0, - } -} - -// 3. 验证参数(可选) -func (f *StatefulFunction) Validate(args []interface{}) error { - return f.ValidateArgCount(args) -} - -// 4. 执行函数 -func (f *StatefulFunction) Execute(ctx *functions.FunctionContext, args []interface{}) (interface{}, error) { - f.mutex.Lock() - defer f.mutex.Unlock() + } +} + +// 3. Verification parameters (optional) +func (f *StatefulFunction) Validate(args []interface{}) error { + return f.ValidateArgCount(args) +} + +// 4. Execute the function +func (f *StatefulFunction) Execute(ctx *functions.FunctionContext, args []interface{}) (interface{}, error) { + f.mutex.Lock() + defer f.mutex.Unlock() f.counter++ - return f.counter, nil -} - -// 5. 注册函数 -func init() { - functions.Register(NewStatefulFunction()) -} -``` - -## 📊 聚合函数 - -聚合函数实现 `AggregatorFunction` 接口(`New`/`Add`/`Result`/`Reset`/`Clone`),用 `functions.Register` 注册一处即可——适配器自动接通,无需 `aggregator.Register`。 - -```go -import ( + return f.counter, nil +} + +// 5. Register functions +func init() { + functions.Register(NewStatefulFunction()) +} +``` + +## 📊 Aggregation Function + +The aggregator function implements `AggregatorFunction` interfaces (`New` / `Add` / `Result` / `Reset` / `Clone`), registering one place with `functions. Register` — adapters automatically connect without `aggregator. Register`. + +```go +import ( "math" "github.com/rulego/streamsql/functions" - "github.com/rulego/streamsql/utils/cast" -) - -// GeometricMean 完整实现 AggregatorFunction -type GeometricMean struct { - *functions.BaseFunction - values []float64 + "github.com/rulego/streamsql/utils/cast" +) + +// GeometricMean Fully implement AggregatorFunction +type GeometricMean struct { + *functions.BaseFunction + values []float64 } func NewGeometricMean() *GeometricMean { return &GeometricMean{BaseFunction: functions.NewBaseFunction( "geometric_mean", functions.TypeAggregation, "统计聚合", "几何平均数", 1, -1)} } - -func (f *GeometricMean) Validate(args []any) error { return f.ValidateArgCount(args) } -func (f *GeometricMean) Execute(ctx *functions.FunctionContext, args []any) (any, error) { - return nil, nil // 聚合走 Add/Result,Execute 仅满足接口 -} -func (f *GeometricMean) New() functions.AggregatorFunction { return &GeometricMean{BaseFunction: f.BaseFunction} } -func (f *GeometricMean) Add(value any) { + +func (f *GeometricMean) Validate(args []any) error { return f.ValidateArgCount(args) } +func (f *GeometricMean) Execute(ctx *functions.FunctionContext, args []any) (any, error) { + return nil, nil // Aggregation follows Add/Result, Execute only meets interface requirements +} +func (f *GeometricMean) New() functions.AggregatorFunction { return &GeometricMean{BaseFunction: f.BaseFunction} } +func (f *GeometricMean) Add(value any) { if v, err := cast.ToFloat64E(value); err == nil && v > 0 { f.values = append(f.values, v) } @@ -253,72 +253,72 @@ func (f *GeometricMean) Clone() functions.AggregatorFunction { } func init() { - functions.Register(NewGeometricMean()) -} - -// SQL使用 -// SELECT device, geometric_mean(value) AS geo_mean FROM stream GROUP BY device -``` - -## 🔧 函数管理 - -### 查看注册的函数 - -```go -// 列出所有函数 -allFunctions := functions.ListAll() -for name, fn := range allFunctions { - fmt.Printf("函数: %s (%s) - %s\n", name, fn.GetType(), fn.GetDescription()) -} - -// 按类型查看 -mathFunctions := functions.GetByType(functions.TypeMath) -for _, fn := range mathFunctions { - fmt.Printf("数学函数: %s\n", fn.GetName()) -} - -// 查找特定函数 -if fn, exists := functions.Get("square"); exists { - fmt.Printf("找到函数: %s\n", fn.GetDescription()) -} -``` - -### 注销函数 - -```go -// 注销函数 -success := functions.Unregister("my_function") -if success { - fmt.Println("函数注销成功") -} -``` - -## 🎯 完整示例 - -### 创建温度转换函数 - -```go -package main + functions.Register(NewGeometricMean()) +} + +// SQL use +// SELECT device, geometric_mean(value) AS geo_mean FROM stream GROUP BY device +``` + +## 🔧 Function Management + +### Check the registered functions + +```go +// List all functions +allFunctions := functions.ListAll() +for name, fn := range allFunctions { + fmt.Printf("函数: %s (%s) - %s\n", name, fn.GetType(), fn.GetDescription()) +} + +// View by type +mathFunctions := functions.GetByType(functions.TypeMath) +for _, fn := range mathFunctions { + fmt.Printf("数学函数: %s\n", fn.GetName()) +} + +// Find a specific function +if fn, exists := functions.Get("square"); exists { + fmt.Printf("找到函数: %s\n", fn.GetDescription()) +} +``` + +### Cancel Function + +```go +// Cancel function +success := functions.Unregister("my_function") +if success { + fmt.Println("函数注销成功") +} +``` + +## 🎯 Complete Example + +### Creating a temperature conversion function + +```go +package main import ( "fmt" "time" "github.com/rulego/streamsql" "github.com/rulego/streamsql/functions" -) - -func main() { - // 1. 注册自定义函数 - registerCustomFunctions() - - // 2. 创建StreamSQL实例 - ssql := streamsql.New() - defer ssql.Stop() - - // 3. 执行SQL - sql := ` - SELECT - device, +) + +func main() { + // 1. Register custom functions + registerCustomFunctions() + + // 2. Create StreamSQL instance + ssql := streamsql.New() + defer ssql.Stop() + + // 3. Execute SQL + sql := ` + SELECT + device, celsius_to_fahrenheit(temperature) as temp_f, format_temperature(temperature, 'C') as formatted_temp FROM stream @@ -326,28 +326,28 @@ func main() { err := ssql.Execute(sql) if err != nil { - panic(err) - } - - // 4. 添加结果监听 - ssql.Stream().AddSink(func(result interface{}) { - fmt.Printf("结果: %v\n", result) - }) - - // 5. 添加数据 - ssql.AddData(map[string]interface{}{ - "device": "thermometer1", - "temperature": 25.0, + panic(err) + } + + // 4. Add result monitoring + ssql.Stream().AddSink(func(result interface{}) { + fmt.Printf("结果: %v\n", result) + }) + + // 5. Add data + ssql.AddData(map[string]interface{}{ + "device": "thermometer1", + "temperature": 25.0, }) time.Sleep(time.Second) -} - -func registerCustomFunctions() { - // 摄氏度转华氏度 - functions.RegisterCustomFunction( - "celsius_to_fahrenheit", - functions.TypeMath, +} + +func registerCustomFunctions() { + // Celsius degrees are converted to Fahrenheit degrees + functions.RegisterCustomFunction( + "celsius_to_fahrenheit", + functions.TypeMath, "温度转换", "摄氏度转华氏度", 1, 1, @@ -357,13 +357,13 @@ func registerCustomFunctions() { return nil, err } return celsius*9/5 + 32, nil - }, - ) - - // 温度格式化 - functions.RegisterCustomFunction( - "format_temperature", - functions.TypeString, + }, + ) + + // Temperature formatting + functions.RegisterCustomFunction( + "format_temperature", + functions.TypeString, "格式化函数", "格式化温度显示", 2, 2, @@ -381,110 +381,110 @@ func registerCustomFunctions() { return fmt.Sprintf("%.1f°%s", temp, unit), nil }, ) -} -``` - -## 🚨 最佳实践 - -### 1. 错误处理 - -```go -func (f *MyFunction) Execute(ctx *functions.FunctionContext, args []interface{}) (interface{}, error) { - // 参数数量检查 - if len(args) == 0 { - return nil, fmt.Errorf("至少需要1个参数") - } - - // 类型转换 - val, err := cast.ToFloat64E(args[0]) - if err != nil { - return nil, fmt.Errorf("参数类型错误: %v", err) - } - - // 业务逻辑验证 - if val < 0 { - return nil, fmt.Errorf("参数必须为非负数") - } +} +``` + +## 🚨 Best Practices + +### 1. Error handling + +```go +func (f *MyFunction) Execute(ctx *functions.FunctionContext, args []interface{}) (interface{}, error) { + // Parameter quantity check + if len(args) == 0 { + return nil, fmt.Errorf("至少需要1个参数") + } + + // Type conversion + val, err := cast.ToFloat64E(args[0]) + if err != nil { + return nil, fmt.Errorf("参数类型错误: %v", err) + } + + // Business logic validation + if val < 0 { + return nil, fmt.Errorf("参数必须为非负数") + } return math.Sqrt(val), nil -} -``` - -### 2. 性能优化 - -```go -type CachedFunction struct { +} +``` + +### 2. Performance optimization + +```go +type CachedFunction struct { *functions.BaseFunction cache map[string]interface{} mutex sync.RWMutex } -func (f *CachedFunction) Execute(ctx *functions.FunctionContext, args []interface{}) (interface{}, error) { - key := fmt.Sprintf("%v", args) - - // 检查缓存 - f.mutex.RLock() - if result, exists := f.cache[key]; exists { - f.mutex.RUnlock() +func (f *CachedFunction) Execute(ctx *functions.FunctionContext, args []interface{}) (interface{}, error) { + key := fmt.Sprintf("%v", args) + + // Check the cache + f.mutex.RLock() + if result, exists := f.cache[key]; exists { + f.mutex.RUnlock() return result, nil - } - f.mutex.RUnlock() - - // 计算结果 - result := f.calculate(args) - - // 缓存结果 - f.mutex.Lock() - f.cache[key] = result - f.mutex.Unlock() + } + f.mutex.RUnlock() + + // Calculation results + result := f.calculate(args) + + // Cache the results + f.mutex.Lock() + f.cache[key] = result + f.mutex.Unlock() return result, nil -} -``` - -### 3. 线程安全 - -```go -type ThreadSafeFunction struct { +} +``` + +### 3. Thread safety + +```go +type ThreadSafeFunction struct { *functions.BaseFunction state map[string]interface{} mutex sync.RWMutex } func (f *ThreadSafeFunction) Execute(ctx *functions.FunctionContext, args []interface{}) (interface{}, error) { - f.mutex.Lock() - defer f.mutex.Unlock() - - // 安全地修改状态 - f.state["counter"] = f.state["counter"].(int) + 1 - - return f.state["counter"], nil -} -``` - -## 📝 测试你的函数 - -```go -func TestMyCustomFunction(t *testing.T) { - // 注册函数 - err := functions.RegisterCustomFunction("test_func", functions.TypeMath, "测试", "测试函数", 1, 1, - func(ctx *functions.FunctionContext, args []interface{}) (interface{}, error) { - val, err := cast.ToFloat64E(args[0]) + f.mutex.Lock() + defer f.mutex.Unlock() + + // Safely modify the state + f.state["counter"] = f.state["counter"].(int) + 1 + + return f.state["counter"], nil +} +``` + +## 📝 Test your function + +```go +func TestMyCustomFunction(t *testing.T) { + // Register the function + err := functions.RegisterCustomFunction("test_func", functions.TypeMath, "测试", "测试函数", 1, 1, + func(ctx *functions.FunctionContext, args []interface{}) (interface{}, error) { + val, err := cast.ToFloat64E(args[0]) return val * 2, err }) - assert.NoError(t, err) - defer functions.Unregister("test_func") - - // 获取并测试函数 - fn, exists := functions.Get("test_func") - assert.True(t, exists) - + assert.NoError(t, err) + defer functions.Unregister("test_func") + + // Get and test the function + fn, exists := functions.Get("test_func") + assert.True(t, exists) + ctx := &functions.FunctionContext{Data: make(map[string]interface{})} result, err := fn.Execute(ctx, []interface{}{5.0}) assert.NoError(t, err) assert.Equal(t, 10.0, result) -} -``` - -通过这个快速入门指南,你已经掌握了StreamSQL自定义函数的基本用法。现在可以开始创建自己的函数来扩展框架功能! \ No newline at end of file +} +``` + +With this quick start guide, you have mastered the basic usage of StreamSQL custom functions. Now you can start creating your own functions to expand the framework's capabilities! diff --git a/docs/FUNCTION_VALIDATION.md b/docs/FUNCTION_VALIDATION.md index d9bac14..70605c5 100644 --- a/docs/FUNCTION_VALIDATION.md +++ b/docs/FUNCTION_VALIDATION.md @@ -1,135 +1,135 @@ -# 函数验证功能 +# Function validation function -## 概述 +## Overview -StreamSQL 现在支持在解析阶段对函数进行验证,能够检测并报告未知函数的使用,提供更好的错误处理和用户体验。 +StreamSQL Now supports function validation during the parsing phase, detecting and reporting unknown function usage, providing better error handling and user experience. -## 功能特性 +## Functional Features -### 1. 函数存在性检查 +### 1. Function existence check -在 SQL 解析过程中,系统会自动验证以下位置的函数调用: -- SELECT 子句中的函数 -- WHERE 子句中的函数 -- HAVING 子句中的函数 +During SQL parsing, the system automatically validates function calls at the following locations: +- SELECT Functions in clauses +- WHERE Functions in clauses +- HAVING Functions in clauses -### 2. 支持的函数类型 +### 2. Supported function types -验证器能够识别以下类型的函数: -- **内置数学函数**: `abs`, `sqrt`, `sin`, `cos`, `tan`, `floor`, `ceil`, `round`, `log`, `log10`, `exp`, `pow`, `mod` -- **注册的自定义函数**: 通过 `functions.Register()` 注册的函数 -- **expr-lang 函数**: 通过 expr-lang 桥接的函数 +Validators can recognize the following types of functions: +- **Built-in mathematical functions**: `abs`, `sqrt`, `sin`, `cos`, `tan`, `floor`, `ceil`, `round`, `log`, `log10`, `exp`, `pow`, `mod` +- **Registered Custom Function**: Function registered via `functions.Register()` +- **expr-lang Function**: Functions bridged via expr-lang -### 3. 错误类型 +### 3. Error types -新增了专门的错误类型 `ErrorTypeUnknownFunction` 来标识未知函数错误。 +A dedicated error type `ErrorTypeUnknownFunction` has been added to identify unknown function errors. -### 4. 智能建议 +### 4. Smart suggestions -当检测到未知函数时,系统会提供有用的建议: -- 常见拼写错误的纠正建议 -- 函数注册和使用的通用指导 +When an unknown function is detected, the system provides useful suggestions: +- Suggestions for correcting common spelling mistakes +- General guidance for function registration and usage -## 使用示例 +## Usage Examples -### 正确的函数使用 +### Correct Use of Functions ```go -// 内置函数 +// Built-in functions ssql := streamsql.New() err := ssql.Execute("SELECT abs(temperature) FROM stream") // err == nil -// 嵌套函数 +// Nested functions err = ssql.Execute("SELECT sqrt(abs(temperature)) FROM stream") // err == nil ``` -### 未知函数错误 +### Unknown function error ```go ssql := streamsql.New() err := ssql.Execute("SELECT unknown_func(temperature) FROM stream") // err != nil -// err.Error() 包含 "Unknown function 'unknown_func'" +// err.Error() contains "Unknown function 'unknown_func'" ``` -### 自定义函数注册 +### Custom function registration ```go -// 注册自定义函数 +// Register custom functions functions.Register("custom_func", func(args ...interface{}) (interface{}, error) { - // 函数实现 + // Function implementation return args[0], nil }) -// 现在可以使用自定义函数 +// Custom functions can now be used ssql := streamsql.New() err := ssql.Execute("SELECT custom_func(temperature) FROM stream") // err == nil ``` -## 错误处理 +## Error Handling -### 错误信息格式 +### Error message format -未知函数错误包含以下信息: -- 错误类型: `ErrorTypeUnknownFunction` -- 错误消息: 包含具体的未知函数名 -- 位置信息: 函数在 SQL 中的位置 -- 建议: 可能的解决方案 +Unknown function errors contain the following information: +- Error type: `ErrorTypeUnknownFunction` +- Error message: Contains the specific unknown function name +- Position information: The position of the function in SQL +- Suggestion: Possible solutions -### 错误恢复 +### Error recovery -函数验证错误是可恢复的,解析器会继续处理其他部分的 SQL,收集所有可能的错误。 +Function validation errors are recoverable, and the parser continues to process SQL in other parts, collecting all possible errors. -## 实现细节 +## Implementation details -### 核心组件 +### Core Components -1. **FunctionValidator**: 主要的函数验证器 - - `ValidateExpression()`: 验证表达式中的函数 - - `extractFunctionCalls()`: 提取函数调用 - - `isBuiltinFunction()`: 检查内置函数 - - `isKeyword()`: 过滤 SQL 关键字 +1. **FunctionValidator**: The main function validator + - `ValidateExpression()`: Verify functions in the expression + - `extractFunctionCalls()`: Extract function calls + - `isBuiltinFunction()`: Check the built-in functions + - `isKeyword()`: Filter SQL keywords -2. **错误类型扩展**: - - `ErrorTypeUnknownFunction`: 新的错误类型 - - `CreateUnknownFunctionError()`: 创建未知函数错误 - - `generateFunctionSuggestions()`: 生成建议 +2. **Error Type Extension**: + - `ErrorTypeUnknownFunction`: New error type + - `CreateUnknownFunctionError()`: Creates an unknown function error + - `generateFunctionSuggestions()`: Generate suggestions -3. **解析器集成**: - - 在 `parseSelect()` 中验证 SELECT 字段 - - 在 `parseWhere()` 中验证 WHERE 条件 - - 在 `parseHaving()` 中验证 HAVING 条件 +3. **Parser Integration**: + - Validate the SELECT field in the `parseSelect()` + - Verify WHERE conditions in `parseWhere()` + - Verify HAVING conditions in `parseHaving()` -### 正则表达式模式 +### Regular Expression Pattern -函数调用检测使用正则表达式 `([a-zA-Z_][a-zA-Z0-9_]*)\s*\(` 来匹配: -- 以字母或下划线开头的标识符 -- 后跟可选的空白字符 -- 然后是左括号 +Function call detection uses regular expressions `([a-zA-Z_][a-zA-Z0-9_]*)\s*\(` to match: +- Identifiers starting with letters or underscores +- Optional whitespace characters followed by the option +- Then the left parenthesis -### 关键字过滤 +### Keyword Filtering -验证器会过滤掉 SQL 关键字,避免将 `CASE(...)` 或 `WHEN(...)` 误识别为函数调用。 +The validator filters out SQL keywords to prevent `CASE(.)` or `WHEN(.)` from being mistakenly identified as function calls. -## 配置选项 +## Configuration options -目前函数验证是默认启用的,无需额外配置。未来可能会添加以下配置选项: -- 禁用函数验证 -- 自定义验证规则 -- 扩展内置函数列表 +Currently, function validation is enabled by default and requires no additional configuration. The following configuration options may be added in the future: +- Disable function validation +- Custom validation rules +- Expand the built-in function list -## 性能考虑 +## Performance considerations -- 函数验证在解析阶段进行,不影响运行时性能 -- 正则表达式匹配针对表达式长度进行了优化 -- 错误收集使用高效的数据结构 +- Function verification is performed during the parsing phase and does not affect runtime performance +- Regular expression matching has been optimized for expression length +- Error collection using efficient data structures -## 测试覆盖 +## Test coverage -功能包含完整的测试覆盖: -- 单元测试: `function_validator_test.go` -- 集成测试: `streamsql_validation_test.go` -- 错误处理测试: `error_test.go` 中的相关用例 +Features include comprehensive testing coverage: +- Unit Test: `function_validator_test.go` +- Integration Testing: `streamsql_validation_test.go` +- Error handling testing: Relevant use cases in `error_test.go` diff --git a/docs/NEGATIVE_NUMBER_SUPPORT.md b/docs/NEGATIVE_NUMBER_SUPPORT.md index 4e6aba3..cf02327 100644 --- a/docs/NEGATIVE_NUMBER_SUPPORT.md +++ b/docs/NEGATIVE_NUMBER_SUPPORT.md @@ -1,184 +1,184 @@ -# StreamSQL 负数支持文档 - -## 概述 - -StreamSQL 现在全面支持负数在 CASE 表达式中的使用。本文档总结了负数支持的完善情况、支持范围和使用建议。 - -## ✅ 已支持的负数用法 - -### 1. 基本负数常量 - -```sql --- CASE 表达式中的负数常量 -CASE WHEN temperature > 0 THEN 1 ELSE -1 END - --- 负数小数 -CASE WHEN temperature > 0 THEN 1.5 ELSE -2.5 END - --- 负零 -CASE WHEN temperature = -0 THEN 1 ELSE 0 END -``` - -### 2. 比较运算符后的负数 - -```sql --- 比较运算符后直接跟负数 -CASE WHEN temperature < -10 THEN 'FREEZING' ELSE 'NORMAL' END -CASE WHEN temperature >= -5.5 THEN 'ABOVE' ELSE 'BELOW' END -CASE WHEN temperature > -20 THEN 'WARM' ELSE 'COLD' END -``` - -### 3. 简单 CASE 表达式中的负数 - -```sql --- 简单 CASE 中使用负数作为匹配值 -CASE temperature - WHEN -10 THEN 'FROZEN' - WHEN -5 THEN 'COLD' +# StreamSQL Negative support document + +## Overview + +StreamSQL Now fully supports the use of negative numbers in CASE expressions. This document summarizes the improvement status, scope of support, and usage recommendations for negative number support. + +## ✅ Supported Negative Number Usage + +### 1. Basic negative constants + +```sql +-- CASE Negative constants in expressions +CASE WHEN temperature > 0 THEN 1 ELSE -1 END + +-- Negative decimal +CASE WHEN temperature > 0 THEN 1.5 ELSE -2.5 END + +-- Negative zero +CASE WHEN temperature = -0 THEN 1 ELSE 0 END +``` + +### 2. Compare the negative number after the operator + +```sql +-- After comparing the operator, just follow the negative number +CASE WHEN temperature < -10 THEN 'FREEZING' ELSE 'NORMAL' END +CASE WHEN temperature >= -5.5 THEN 'ABOVE' ELSE 'BELOW' END +CASE WHEN temperature > -20 THEN 'WARM' ELSE 'COLD' END +``` + +### 3. A negative number in a simple CASE expression + +```sql +-- In simple CASE, negative numbers are used as matching values +CASE temperature + WHEN -10 THEN 'FROZEN' + WHEN -5 THEN 'COLD' WHEN 0 THEN 'ZERO' ELSE 'OTHER' -END -``` - -### 4. 算术表达式中的负数 - -```sql --- 括号内的负数运算 -CASE WHEN temperature + (-10) > 0 THEN 1 ELSE 0 END -CASE WHEN (temperature * -1) > 10 THEN 1 ELSE 0 END -``` - -## ⚠️ 部分支持或限制 - -### 1. 函数参数中的负数表达式 - -```sql --- 当前不完全支持:函数参数中的负数变量 -CASE WHEN ABS(-temperature) > 10 THEN 1 ELSE 0 END -- ❌ - --- 推荐替代方案:使用括号或先计算 -CASE WHEN ABS(temperature * -1) > 10 THEN 1 ELSE 0 END -- ✅ -``` - -### 2. BETWEEN 语句中的负数范围 - -```sql --- 当前不支持:BETWEEN 与负数组合 -CASE WHEN temperature BETWEEN -20 AND -10 THEN 1 ELSE 0 END -- ❌ - --- 推荐替代方案:使用比较运算符 -CASE WHEN temperature >= -20 AND temperature <= -10 THEN 1 ELSE 0 END -- ✅ -``` - -### 3. SQL 中的空格分隔负数 - -```sql --- 避免在 SQL 中使用空格分隔的负数 -SELECT CASE WHEN temperature < - 10 THEN 'COLD' END -- ❌ 解析问题 - --- 推荐写法:紧密连接或使用括号 -SELECT CASE WHEN temperature < -10 THEN 'COLD' END -- ✅ -SELECT CASE WHEN temperature < (-10) THEN 'COLD' END -- ✅ -``` - -## 🔧 技术实现 - -### 词法分析器增强 - -1. **智能负数识别**: - - 识别比较运算符后的负数(`<`, `>`, `<=`, `>=`, `==`, `!=`) - - 支持逻辑运算符后的负数(`AND`, `OR`) - - 支持 CASE 关键字后的负数(`WHEN`, `THEN`, `ELSE`) - -2. **连续运算符检查优化**: - - 允许比较运算符后跟负数的合法组合 - - 智能区分负数与减号运算符 - -3. **空格处理**: - - 正确处理空格分隔的负数标记 - - 改进 token 化过程以支持各种负数格式 - -### 表达式求值增强 - -1. **负数常量解析**:完全支持负整数和负小数 -2. **类型转换**:正确处理负数的数值转换 -3. **NULL 值处理**:负数与 NULL 值的正确交互 - -## 📊 测试覆盖 - -### 表达式级别测试 - -- ✅ 负数常量在 THEN/ELSE 中 -- ✅ 负数常量在 WHEN 条件中 -- ✅ 负数小数支持 -- ✅ 负数在算术表达式中 -- ✅ 负数在简单 CASE 中 -- ✅ 负零处理 - -### SQL 集成测试 - -- ✅ 完整 SQL 语句中的负数支持 -- ✅ 非聚合查询中的负数表达式 -- ✅ 聚合查询中的负数处理 - -## 🎯 使用建议 - -### 1. 推荐的负数写法 - -```sql --- ✅ 推荐:紧密连接的负数 -CASE WHEN temperature < -10 THEN 'FREEZING' END - --- ✅ 推荐:括号包围的负数(最安全) -CASE WHEN temperature < (-10) THEN 'FREEZING' END - --- ✅ 推荐:负数小数 -CASE WHEN temperature < -10.5 THEN 'FREEZING' END -``` - -### 2. 避免的写法 - -```sql --- ❌ 避免:空格分隔的负数 -CASE WHEN temperature < - 10 THEN 'FREEZING' END - --- ❌ 避免:复杂的负数表达式在函数中 -CASE WHEN ABS(-temperature) > 10 THEN 1 END -``` - -### 3. 最佳实践 - -1. **使用括号**:当不确定负数解析时,总是使用括号包围负数 -2. **避免空格**:在负号和数字之间不要添加空格 -3. **测试验证**:对包含负数的复杂表达式进行充分测试 -4. **版本兼容**:确保使用的 StreamSQL 版本支持所需的负数功能 - -## 🚀 未来改进计划 - -1. **完全支持函数参数中的负数表达式** -2. **支持 BETWEEN 语句中的负数范围** -3. **改进 SQL 解析器对空格分隔负数的处理** -4. **扩展负数支持到更多数学和字符串函数** - -## 示例代码 - -```go -package main +END +``` + +### 4. Negative numbers in arithmetic expressions + +```sql +-- Negative number operations in parentheses +CASE WHEN temperature + (-10) > 0 THEN 1 ELSE 0 END +CASE WHEN (temperature * -1) > 10 THEN 1 ELSE 0 END +``` + +## ⚠️ Partial support or limitations + +### 1. Negative number expressions in function parameters + +```sql +-- Currently not fully supported: negative number variables in function parameters +CASE WHEN ABS(-temperature) > 10 THEN 1 ELSE 0 END -- ❌ + +-- Recommended alternative: use parentheses or calculate first +CASE WHEN ABS(temperature * -1) > 10 THEN 1 ELSE 0 END -- ✅ +``` + +### 2. BETWEEN The range of negative numbers in a statement + +```sql +-- Currently not supported: BETWEEN with negative number combinations +CASE WHEN temperature BETWEEN -20 AND -10 THEN 1 ELSE 0 END -- ❌ + +-- Recommended alternative: Use comparison operators +CASE WHEN temperature >= -20 AND temperature <= -10 THEN 1 ELSE 0 END -- ✅ +``` + +### 3. Spaces in SQL separate negative numbers + +```sql +-- Avoid using space-separated negative numbers in SQL +SELECT CASE WHEN temperature < - 10 THEN 'COLD' END -- ❌ Analyze the problem + +-- Recommended method: tightly connect or use parentheses +SELECT CASE WHEN temperature < -10 THEN 'COLD' END -- ✅ +SELECT CASE WHEN temperature < (-10) THEN 'COLD' END -- ✅ +``` + +## 🔧 Technical Implementation + +### Lexical analyzer enhancement + +1. **Intelligent Negative Number Recognition**: + - Identify negative numbers after comparison operators (`<`, `>`, `<=`, `>=`, `==`, `!=`) + - Supports negative numbers after logical operators (`AND`, `OR`) + - Supports negative numbers after CASE keywords (`WHEN`, `THEN`, `ELSE`) + +2. **Continuous Operator Check Optimization**: + - Allows legitimate combinations of comparison operators followed by negative numbers + - Intelligently distinguishes between negative and minus operators + +3. **Spaces**: + - Properly handle negative number markers separated by spaces + - Improved token process to support various negative number formats + +### Expression Evaluation Enhancement + +1. **Negative Number Constants Analysis**: Fully supports negative integers and negative decimals +2. **Type Conversion**: Correctly handle numerical conversion of negative numbers +3. **NULL Value Processing**: Correct interaction between negative and NULL values + +## 📊 Test coverage + +### Expression-level testing + +- ✅ Negative constants in THEN/ELSE +- ✅ Negative constants in WHEN condition +- ✅ Negative decimal support +- ✅ Negative numbers in arithmetic expressions +- ✅ Negative numbers in simple CASE +- ✅ Negative zero processing + +### SQL Integration Testing + +- ✅ Negative number support in full SQL statements +- ✅ Negative expressions in non-aggregated queries +- ✅ Handling negative numbers in aggregated queries + +## 🎯 Usage Recommendations + +### 1. Recommended ways to write negative numbers + +```sql +-- ✅ Recommendation: Tightly connected negative numbers +CASE WHEN temperature < -10 THEN 'FREEZING' END + +-- ✅ Recommendation: Negative numbers enclosed in parentheses (safest) +CASE WHEN temperature < (-10) THEN 'FREEZING' END + +-- ✅ Recommended: Negative decimals +CASE WHEN temperature < -10.5 THEN 'FREEZING' END +``` + +### 2. Avoid writing it + +```sql +-- ❌ Avoid: negative numbers separated by spaces +CASE WHEN temperature < - 10 THEN 'FREEZING' END + +-- ❌ Avoid: Complex negative number expressions in functions +CASE WHEN ABS(-temperature) > 10 THEN 1 END +``` + +### 3. Best practices + +1. **Use parentheses**: When negative number analysis is uncertain, always use parentheses to enclose negative numbers +2. **Avoid spaces**: Do not add spaces between the negative sign and the number +3. **Test and Verify**: Thoroughly test complex expressions containing negative numbers +4. **version compatible with**: Make sure the StreamSQL version you use supports the required negative number feature + +## 🚀 Future Improvement Plans + +1. **Fully supports negative number expressions** in function parameters +2. **supports** of negative numbers in BETWEEN statements +3. **Improvements SQL** parsers for spaces to separate negative numbers +4. **Expand negative numbers to support more mathematical and string function** + +## Example code + +```go +package main import ( "fmt" "github.com/rulego/streamsql" -) - -func main() { - // 创建 StreamSQL 实例 - sql := streamsql.New() - defer sql.Stop() - - // 包含负数的 SQL 查询 - query := ` - SELECT deviceId, - temperature, +) + +func main() { + // Create StreamSQL instances + sql := streamsql.New() + defer sql.Stop() + + // SQL queries containing negative numbers + query := ` + SELECT deviceId, + temperature, CASE WHEN temperature < -10 THEN 'FREEZING' WHEN temperature < 0 THEN 'COLD' @@ -189,25 +189,25 @@ func main() { WHEN temperature > 0 THEN temperature ELSE (-1.0) END as adjusted_temp - FROM stream - ` - - // 执行查询 - err := sql.Execute(query) - if err != nil { - fmt.Printf("执行失败: %v\n", err) - return - } - - // 添加数据处理器 - sql.AddSink(func(result interface{}) { - fmt.Printf("结果: %+v\n", result) - }) - - // 添加测试数据 - testData := []map[string]interface{}{ - {"deviceId": "sensor1", "temperature": -15.0}, - {"deviceId": "sensor2", "temperature": -5.0}, + FROM stream + ` + + // Execute the query + err := sql.Execute(query) + if err != nil { + fmt.Printf("执行失败: %v\n", err) + return + } + + // Add data processor + sql.AddSink(func(result interface{}) { + fmt.Printf("结果: %+v\n", result) + }) + + // Add test data + testData := []map[string]interface{}{ + {"deviceId": "sensor1", "temperature": -15.0}, + {"deviceId": "sensor2", "temperature": -5.0}, {"deviceId": "sensor3", "temperature": 0.0}, {"deviceId": "sensor4", "temperature": 10.0}, } @@ -217,9 +217,9 @@ func main() { } } ``` - ---- - -**更新日期**: 2025-06-17 -**版本**: StreamSQL v0.x -**作者**: StreamSQL 开发团队 \ No newline at end of file + +--- + +**Last Updated**: 2025-06-17 +**Version**: StreamSQL v0.x +**Author**: StreamSQL Development Team diff --git a/docs/NESTED_FIELD_ACCESS.md b/docs/NESTED_FIELD_ACCESS.md index b8b3d0c..1004feb 100644 --- a/docs/NESTED_FIELD_ACCESS.md +++ b/docs/NESTED_FIELD_ACCESS.md @@ -1,77 +1,77 @@ -# 嵌套字段访问功能 - -StreamSQL 支持对嵌套结构数据进行查询和聚合操作,提供了强大的字段访问语法,包括点号访问、数组索引、Map键访问等复杂操作。 - -## 功能特性 - -- **点号语法访问**:支持 `field.subfield.property` 的访问方式 -- **数组索引访问**:支持 `array[0]`、`array[-1]` 等数组元素访问 -- **Map键访问**:支持 `map['key']` 和 `map["key"]` 语法访问Map值 -- **混合复杂访问**:组合使用点号、数组索引、Map键进行深层嵌套访问 -- **负数索引**:支持负数索引从数组末尾开始访问元素 -- **完整 SQL 支持**:SELECT、WHERE、GROUP BY、聚合函数中都可以使用嵌套字段 -- **类型兼容**:支持 `map[string]interface{}` 和结构体类型的嵌套访问 -- **向后兼容**:现有的平坦字段访问方式保持不变 - -## 支持的访问语法 - -### 1. 基本点号访问 -```sql -SELECT device.info.name, - sensor.temperature -FROM stream -``` - -### 2. 数组索引访问 -```sql --- 正数索引(从0开始) -SELECT data[0] as first_item, - sensors[1].temperature as second_sensor_temp, - matrix[2][1] as matrix_element -FROM stream - --- 负数索引(从末尾开始) -SELECT readings[-1] as latest_reading, - history[-2] as second_last_event -FROM stream -``` - -### 3. Map键访问 -```sql --- 字符串键访问 -SELECT config['host'] as server_host, - settings["timeout"] as timeout_value, - metadata['version'] as app_version -FROM stream -``` - -### 4. 混合复杂访问 -```sql --- 组合使用各种访问方式 -SELECT users[0].profile['name'] as user_name, - data.items[1][0] as nested_value, - floors[0].rooms[2]['name'] as room_name, +# Nested field access feature + +StreamSQL Supports querying and aggregating nested structure data, providing powerful field access syntax including dot access, array indexing, Map key access, and other complex operations. + +## Functional Features + +- **Dotted Syntax Access**: Supports `field.subfield.property` access +- **Array Index Access**: Supports access to array elements such as `array[0]` and `array[-1]` +- **Map key access to**: Supports `map['key']` and `map["key"]` syntax access to Map values +- **Hybrid complex access**: Combines dot numbers, array indexes, and Map keys for deep nested access +- **Negative Index**: Supports accessing elements from the end of the array using negative indexes +- **Full SQL supports nested fields in**: SELECT, WHERE, GROUP BY, and aggregate functions +- **type compatibility**: Supports nested access to `map[string]interface{}` and struct types +- **Backward Compatibility**: The existing flat field access method remains unchanged + +## Supported access syntax + +### 1. Basic point number access +```sql +SELECT device.info.name, + sensor.temperature +FROM stream +``` + +### 2. Array index access +```sql +-- Positive Index (starting from 0) +SELECT data[0] as first_item, + sensors[1].temperature as second_sensor_temp, + matrix[2][1] as matrix_element +FROM stream + +-- Negative Index (starting from the end) +SELECT readings[-1] as latest_reading, + history[-2] as second_last_event +FROM stream +``` + +### 3. Map Key access +```sql +-- String key access +SELECT config['host'] as server_host, + settings["timeout"] as timeout_value, + metadata['version'] as app_version +FROM stream +``` + +### 4. Hybrid complex access +```sql +-- Combine various access methods +SELECT users[0].profile['name'] as user_name, + data.items[1][0] as nested_value, + floors[0].rooms[2]['name'] as room_name, sensors[0].readings['temperature'] as temp -FROM stream -``` - -### 5. 在聚合中使用 -```sql --- 聚合函数中的复杂字段访问 -SELECT location, - AVG(sensors[0].temperature) as avg_first_sensor_temp, - MAX(sensors[1].humidity) as max_second_sensor_humidity, +FROM stream +``` + +### 5. Used in aggregation +```sql +-- Complex field access in aggregate functions +SELECT location, + AVG(sensors[0].temperature) as avg_first_sensor_temp, + MAX(sensors[1].humidity) as max_second_sensor_humidity, COUNT(*) as device_count FROM stream -GROUP BY location, TumblingWindow('5s') -``` - -## 支持的数据格式 - -### 基本嵌套结构 -```json -{ - "device": { +GROUP BY location, TumblingWindow('5s') +``` + +## Supported Data Formats + +### Basic nested structure +```json +{ + "device": { "info": { "name": "sensor-001", "type": "temperature" @@ -82,26 +82,26 @@ GROUP BY location, TumblingWindow('5s') "temperature": 25.5, "humidity": 60.2 } -} -``` - -### 数组结构 -```json -{ - "device": "工业传感器-001", +} +``` + +### Array Structure +```json +{ + "device": "工业传感器-001", "sensors": [ {"temperature": 25.5, "humidity": 60.2}, {"temperature": 26.8, "humidity": 58.7}, {"temperature": 24.1, "humidity": 62.1} ], "data": ["status_ok", "battery_95%", "signal_strong"] -} -``` - -### Map键结构 -```json -{ - "device_id": "gateway-001", +} +``` + +### Map Key structure +```json +{ + "device_id": "gateway-001", "config": { "host": "192.168.1.100", "port": 8080, @@ -111,13 +111,13 @@ GROUP BY location, TumblingWindow('5s') "enable_ssl": true, "timeout": 30 } -} -``` - -### 复杂混合结构 -```json -{ - "building": "智能大厦A座", +} +``` + +### Complex Hybrid Structures +```json +{ + "building": "智能大厦A座", "floors": [ { "floor_number": 1, @@ -139,108 +139,108 @@ GROUP BY location, TumblingWindow('5s') ] } ] -} -``` - -## 使用示例 - -### 1. 基本嵌套字段查询 - -```sql --- 查询设备信息和传感器数据 -SELECT device.info.name as device_name, - device.location, - sensor.temperature -FROM stream -``` - -### 2. 数组索引访问 - -```sql --- 访问数组中的特定元素 -SELECT device, - sensors[0].temperature as first_sensor_temp, - sensors[1].humidity as second_sensor_humidity, +} +``` + +## Usage Examples + +### 1. Basic nested field queries + +```sql +-- Query device information and sensor data +SELECT device.info.name as device_name, + device.location, + sensor.temperature +FROM stream +``` + +### 2. Array index access + +```sql +-- Access specific elements in the array +SELECT device, + sensors[0].temperature as first_sensor_temp, + sensors[1].humidity as second_sensor_humidity, data[2] as third_data_item -FROM stream -``` - -### 3. Map键访问 - -```sql --- 使用字符串键访问Map数据 -SELECT device_id, - config['host'] as server_host, - config["port"] as server_port, +FROM stream +``` + +### 3. Map Key access + +```sql +-- Use string keys to access Map data +SELECT device_id, + config['host'] as server_host, + config["port"] as server_port, settings['enable_ssl'] as ssl_enabled -FROM stream -``` - -### 4. 负数索引访问 - -```sql --- 使用负数索引访问数组末尾元素 -SELECT device_name, - readings[-1] as latest_reading, - tags[-1] as last_tag -FROM stream -``` - -### 5. 混合复杂访问 - -```sql --- 复杂的嵌套访问组合 -SELECT building, - floors[0].rooms[2]['name'] as first_floor_room3, - floors[1].sensors[0].readings['temperature'] as second_floor_temp, +FROM stream +``` + +### 4. Negative index access + +```sql +-- Use a negative index to access the last element of an array +SELECT device_name, + readings[-1] as latest_reading, + tags[-1] as last_tag +FROM stream +``` + +### 5. Hybrid complex access + +```sql +-- Complex nested access combinations +SELECT building, + floors[0].rooms[2]['name'] as first_floor_room3, + floors[1].sensors[0].readings['temperature'] as second_floor_temp, metadata.building_info['architect'] as architect -FROM stream -``` - -### 6. 条件过滤 - -```sql --- 在WHERE子句中使用复杂字段访问 -SELECT device.info.name, sensor.temperature -FROM stream -WHERE device.location = 'room-A' +FROM stream +``` + +### 6. Conditional filtering + +```sql +-- Use complex fields to access WHERE clauses +SELECT device.info.name, sensor.temperature +FROM stream +WHERE device.location = 'room-A' AND sensors[0].temperature > 25.0 - AND config['enable_monitoring'] = true -``` - -### 7. 聚合查询 - -```sql --- 在聚合中使用复杂字段访问 -SELECT device.location, - AVG(sensors[0].temperature) as avg_temp, - MAX(sensors[1].humidity) as max_humidity, + AND config['enable_monitoring'] = true +``` + +### 7. Aggregated queries + +```sql +-- Use complex field access in aggregation +SELECT device.location, + AVG(sensors[0].temperature) as avg_temp, + MAX(sensors[1].humidity) as max_humidity, COUNT(*) as sensor_count FROM stream GROUP BY device.location, TumblingWindow('1s') -WITH (TIMESTAMP='timestamp', TIMEUNIT='ss') -``` - -## 实际应用示例 - -```go -package main +WITH (TIMESTAMP='timestamp', TIMEUNIT='ss') +``` + +## Practical Application Examples + +```go +package main import ( "fmt" "time" "github.com/rulego/streamsql" -) - -func main() { - // 创建 StreamSQL 实例 - ssql := streamsql.New() - defer ssql.Stop() - - // 执行复杂嵌套字段查询 - rsql := `SELECT device.info.name as device_name, - device.location, - sensors[0].temperature as first_sensor_temp, +) + +func main() { + // Create StreamSQL instances + ssql := streamsql.New() + defer ssql.Stop() + + // Perform complex nested field queries + rsql := `SELECT device.info.name as device_name, + device.location, + sensors[0].temperature as first_sensor_temp, config['host'] as server_host, readings[-1] as latest_reading, AVG(sensors[1].humidity) as avg_humidity @@ -252,18 +252,18 @@ func main() { err := ssql.Execute(rsql) if err != nil { - panic(err) - } - - // 添加数据处理回调 - ssql.Stream().AddSink(func(result interface{}) { - fmt.Printf("聚合结果: %+v\n", result) - }) - - // 添加复杂嵌套结构数据 - testData := map[string]interface{}{ - "device": map[string]interface{}{ - "info": map[string]interface{}{ + panic(err) + } + + // Add data processing callbacks + ssql.Stream().AddSink(func(result interface{}) { + fmt.Printf("聚合结果: %+v\n", result) + }) + + // Add complex nested structure data + testData := map[string]interface{}{ + "device": map[string]interface{}{ + "info": map[string]interface{}{ "name": "temperature-sensor-001", "type": "temperature", }, @@ -284,18 +284,18 @@ func main() { "enable_monitoring": true, }, "readings": []interface{}{18.5, 19.2, 20.1, 23.5}, - "timestamp": time.Now().Unix(), - } - - // 推送数据到流 - ssql.Stream().AddData(testData) -} -``` - -## 输出结果示例 - -```json -[ + "timestamp": time.Now().Unix(), + } + + // Push data to the stream + ssql.Stream().AddData(testData) +} +``` + +## Example output results + +```json +[ { "device.location": "智能温室-A区", "device_name": "temperature-sensor-001", @@ -304,64 +304,64 @@ func main() { "latest_reading": 23.5, "avg_humidity": 59.45 } -] -``` - -## 技术实现 - -复杂嵌套字段访问功能通过以下核心模块实现: - -1. **字段路径解析器**:解析复杂的字段访问路径,支持点号、数组索引、Map键等语法 -2. **访问器引擎**:根据解析结果进行实际的数据访问操作 -3. **词法分析器扩展**:支持方括号、引号等特殊字符的词法分析 -4. **表达式引擎**:在字段访问时检查是否为嵌套字段并使用相应的访问方法 -5. **聚合器增强**:支持嵌套字段作为分组键和聚合目标 -6. **流处理器优化**:在数据处理过程中高效支持复杂字段访问 - -### 支持的字段访问类型 - -- **field**:普通字段访问(`name`、`user.profile`) -- **array_index**:数组索引访问(`data[0]`、`items[-1]`) -- **map_key**:Map键访问(`config['host']`、`settings["timeout"]`) - -### 解析示例 - -``` -字段路径: users[0].profile['name'] +] +``` + +## Technical Implementation + +Complex nested field access functionality is implemented through the following core modules: + +1. **Field Path Parser**: Parses complex field access paths, supports point, array index, Map key, and other syntax +2. **Accessor Engine**: Performs actual data access operations based on parsing results +3. **Lexical analyzer extension**: Supports lexical analysis of special characters such as square brackets and quotation marks +4. **Expression Engine**: Checks whether the field is nested during field access and uses the corresponding access method +5. **Aggregator Enhancement**: Supports nested fields as group keys and aggregation targets +6. **Stream Processor Optimization**: Efficiently supports complex field access during data processing + +### Supported types of field access + +- **field**: Regular field access (`name`, `user.profile`) +- **array_index**: Array index access (`data[0]`, `items[-1]`) +- **map_key**: Map key access (`config['host']`, `settings["timeout"]`) + +### Explanation Example + +``` +字段路径: users[0].profile['name'] 解析结果: - {Type: "field", Name: "users"} - {Type: "array_index", Index: 0} - {Type: "field", Name: "profile"} - - {Type: "map_key", Key: "name", KeyType: "string"} -``` - -## 性能考虑 - -- **复杂度影响**:复杂嵌套访问比简单字段访问略慢,因为需要逐层解析 -- **负数索引成本**:负数索引需要计算数组长度,会有轻微性能影响 -- **缓存机制**:字段路径解析结果会被缓存以提高重复访问性能 -- **建议实践**:在高频查询中避免过深的嵌套层级(建议不超过5层) - -## 注意事项 - -1. **索引范围**:数组索引从0开始,负数索引从-1开始(最后一个元素) -2. **键格式**:Map键访问支持单引号和双引号,键名区分大小写 -3. **错误处理**:访问不存在的索引或键会返回null值,不会抛出异常 -4. **字段名保持**:复杂字段路径在结果中会保持完整格式,建议使用AS别名 -5. **类型安全**:所有访问操作都是类型安全的,支持自动类型推断 -6. **兼容性**:完全向后兼容原有的简单点号访问方式 - -## 错误处理 - -- **解析错误**:无效的字段路径格式会在SQL解析阶段报错 -- **访问错误**:运行时访问不存在的字段会返回null,不会中断查询 -- **类型错误**:在非数组数据上使用数组索引会返回null -- **边界检查**:数组索引超出范围会安全返回null - -## 最佳实践 - -1. **使用别名**:为复杂字段路径提供清晰的别名 -2. **验证数据**:在生产环境中验证输入数据的结构 -3. **合理嵌套**:避免过深的嵌套层级以保持性能 -4. **错误预期**:预期并处理字段不存在的情况 -5. **性能测试**:在大数据量场景下进行性能测试 \ No newline at end of file + - {Type: "map_key", Key: "name", KeyType: "string"} +``` + +## Performance considerations + +- **Complexity Impact**: Complex nested access is slightly slower than simple field access because it requires layer-by-layer parsing +- **Cost of Negative Indexes**: Negative indexes require calculating array length, which may have a slight performance impact +- **Caching Mechanism**: Field path parsing results are cached to improve repeat access performance +- **Recommended Practice**: Avoid overly deep nested layers in high-frequency queries (recommended no more than 5 layers) + +## Notes + +1. **Index Range**: Array index starts from 0, negative index starts at -1 (last element) +2. **Key Format**: Map Key access supports single and double quotes, with case sensitivity +3. **Error Handling**: Accessing non-existent indexes or keys returns null values without throwing exceptions +4. **Keep field names**: Complex field paths will remain fully formatted in the results, so it is recommended to use AS aliases +5. **Type-safe**: All access operations are type-safe and support automatic type inference +6. **Compatibility**: Fully backward compatible with the original simple dot-point access method + +## Error Handling + +- **Parsing Error**: Invalid field path formats will cause errors during the SQL parsing phase +- **Access error**: Accessing fields that do not exist at runtime returns null and does not interrupt the query +- **Type Error**: Using an array index on non-array data returns null +- **Boundary Check**: If the array index goes out of range, it will safely return null + +## Best Practices + +1. **Use Aliases**: Provide clear aliases for complex field paths +2. **Validate Data**: Verify the structure of input data in a production environment +3. **Reasonable nesting**: Avoid overly deep nesting layers to maintain performance +4. **Error Expectation**: Anticipate and handle cases where fields do not exist +5. **Performance Testing**: Conduct performance tests in scenarios with large data volumes diff --git a/docs/PLUGIN_EXAMPLE.md b/docs/PLUGIN_EXAMPLE.md index d7d7a2a..2625d7d 100644 --- a/docs/PLUGIN_EXAMPLE.md +++ b/docs/PLUGIN_EXAMPLE.md @@ -1,11 +1,11 @@ -# StreamSQL 插件式自定义函数快速示例 - -## 🚀 5分钟上手插件式扩展 - -### 1️⃣ 注册自定义函数 - -```go -package main +# StreamSQL Quick example of plugin-style custom functions + +## 🚀 Plug-in Expansion in Just 5 Minutes + +### 1️⃣ Register custom functions + +```go +package main import ( "fmt" @@ -14,19 +14,19 @@ import ( "github.com/rulego/streamsql" "github.com/rulego/streamsql/functions" "github.com/rulego/streamsql/utils/cast" -) - -func main() { - // 🔌 插件式注册 - 数据脱敏函数 - functions.RegisterCustomFunction( - "mask_email", // 函数名 - functions.TypeString, // 函数类型 - "数据脱敏", // 分类 - "邮箱地址脱敏", // 描述 - 1, 1, // 参数数量 - func(ctx *functions.FunctionContext, args []interface{}) (interface{}, error) { - email, _ := cast.ToStringE(args[0]) - parts := strings.Split(email, "@") +) + +func main() { + // 🔌 Plug-in registration - Data anonymization function + functions.RegisterCustomFunction( + "mask_email", // Function name + functions.TypeString, // Function type + "数据脱敏", // Classification + "邮箱地址脱敏", // Description + 1, 1, // Number of parameters + func(ctx *functions.FunctionContext, args []interface{}) (interface{}, error) { + email, _ := cast.ToStringE(args[0]) + parts := strings.Split(email, "@") if len(parts) != 2 { return email, nil } @@ -39,13 +39,13 @@ func main() { return masked + "@" + domain, nil } return email, nil - }, - ) - - // 🔌 插件式注册 - 业务计算函数 - functions.RegisterCustomFunction( - "calculate_score", - functions.TypeMath, + }, + ) + + // 🔌 Plug-in registration - business calculation function + functions.RegisterCustomFunction( + "calculate_score", + functions.TypeMath, "业务计算", "计算用户评分", 2, 2, @@ -53,13 +53,13 @@ func main() { base, _ := cast.ToFloat64E(args[0]) bonus, _ := cast.ToFloat64E(args[1]) return base + bonus*0.1, nil - }, - ) - - // 🔌 插件式注册 - 状态转换函数 - functions.RegisterCustomFunction( - "format_status", - functions.TypeConversion, + }, + ) + + // 🔌 Plug-in registration - status transition function + functions.RegisterCustomFunction( + "format_status", + functions.TypeConversion, "状态转换", "格式化状态显示", 1, 1, @@ -72,20 +72,20 @@ func main() { } }, ) -} -``` - -### 2️⃣ 立即在SQL中使用 - -```go -func demonstrateUsage() { - ssql := streamsql.New() - defer ssql.Stop() - - // 🎯 直接在SQL中使用新注册的函数 - 无需修改任何核心代码! - sql := ` - SELECT - user_id, +} +``` + +### 2️⃣ Use it now in SQL + +```go +func demonstrateUsage() { + ssql := streamsql.New() + defer ssql.Stop() + + // 🎯 Use newly registered functions directly in the SQL—no need to modify any core code! + sql := ` + SELECT + user_id, mask_email(email) as safe_email, format_status(status) as status_display, AVG(calculate_score(base_score, performance)) as avg_score @@ -95,18 +95,18 @@ func demonstrateUsage() { err := ssql.Execute(sql) if err != nil { - panic(err) - } - - // 添加结果监听 - ssql.Stream().AddSink(func(result interface{}) { - fmt.Printf("处理结果: %v\n", result) - }) - - // 添加测试数据 - testData := []map[string]interface{}{ - { - "user_id": "U001", + panic(err) + } + + // Add result monitoring + ssql.Stream().AddSink(func(result interface{}) { + fmt.Printf("处理结果: %v\n", result) + }) + + // Add test data + testData := []map[string]interface{}{ + { + "user_id": "U001", "email": "john.doe@example.com", "status": "1", "base_score": 85.0, @@ -122,96 +122,96 @@ func demonstrateUsage() { } for _, data := range testData { - ssql.AddData(data) - } - - // 等待结果 - time.Sleep(6 * time.Second) -} -``` - -### 3️⃣ 运行结果 - -```json -{ + ssql.AddData(data) + } + + // Waiting for the results + time.Sleep(6 * time.Second) +} +``` + +### 3️⃣ Running results + +```json +{ "user_id": "U001", "safe_email": "jo***e@example.com", "status_display": "✅ 活跃", "avg_score": 86.35 -} -``` - -## 🔥 核心优势 - -### ✅ 完全插件式 -- **无需修改SQL解析器** - 新函数自动识别 -- **无需重启应用** - 运行时动态注册 -- **无需额外配置** - 注册后立即可用 - -### ✅ 智能处理 -- **字符串函数** → 直接处理模式(低延迟) -- **数学函数** → 窗口聚合模式(支持统计) -- **转换函数** → 直接处理模式(实时转换) - -### ✅ 灵活管理 -```go -// 运行时管理 -fn, exists := functions.Get("mask_email") // 查询函数 -mathFuncs := functions.GetByType(functions.TypeMath) // 按类型查询 -allFuncs := functions.ListAll() // 列出所有函数 -success := functions.Unregister("old_function") // 注销函数 -``` - -## 🎯 实际应用场景 - -### 📊 数据脱敏 -```sql -SELECT - mask_email(email) as safe_email, +} +``` + +## 🔥 Core Advantages + +### ✅ Fully plug-in type +- **No modification needed SQL parser** - New functions are automatically recognized +- **No need to restart the app** - Runtime dynamic registration +- **No additional configuration required** - Available immediately upon registration + +### ✅ Intelligent Processing +- **String function** → Direct processing mode (low latency) +- **Mathematical Functions** → Window Aggregation Mode (supports statistics) +- **Conversion Function** → Direct Processing Mode (Real-Time Conversion) + +### ✅ Flexible management +```go +// Runtime management +fn, exists := functions.Get("mask_email") // Query function +mathFuncs := functions.GetByType(functions.TypeMath) // Search by type +allFuncs := functions.ListAll() // List all functions +success := functions.Unregister("old_function") // Cancel function +``` + +## 🎯 Practical Application Scenarios + +### 📊 Data Anonymization +```sql +SELECT + mask_email(email) as safe_email, mask_phone(phone) as safe_phone -FROM user_stream -``` - -### 💼 业务计算 -```sql -SELECT - user_id, +FROM user_stream +``` + +### 💼 Business Calculation +```sql +SELECT + user_id, AVG(calculate_commission(sales, rate)) as avg_commission, SUM(calculate_bonus(performance, level)) as total_bonus FROM sales_stream -GROUP BY user_id, TumblingWindow('1h') -``` - -### 🔄 状态转换 -```sql -SELECT - order_id, +GROUP BY user_id, TumblingWindow('1h') +``` + +### 🔄 State transition +```sql +SELECT + order_id, format_status(status_code) as readable_status, format_priority(priority_level) as priority_display -FROM order_stream -``` - -### 🌐 多语言支持 -```go -// 注册多语言函数 -functions.RegisterCustomFunction("translate", functions.TypeString, ..., - func(ctx *functions.FunctionContext, args []interface{}) (interface{}, error) { - text := args[0].(string) +FROM order_stream +``` + +### 🌐 Multilingual support +```go +// Register multilingual functions +functions.RegisterCustomFunction("translate", functions.TypeString, ..., + func(ctx *functions.FunctionContext, args []interface{}) (interface{}, error) { + text := args[0].(string) lang := args[1].(string) - return translateService.Translate(text, lang), nil - }) - -// SQL中使用 -// SELECT translate(message, 'zh-CN') as chinese_message FROM stream -``` - -## 🏁 总结 - -StreamSQL 的插件式自定义函数系统让你能够: - -1. **🔌 即插即用** - 注册函数后立即在SQL中使用 -2. **🚀 零停机扩展** - 运行时动态增加功能 -3. **🎯 类型智能** - 根据函数类型自动选择最优处理模式 -4. **📈 无限可能** - 支持任意复杂的业务逻辑 - -**真正实现了"写一个函数,SQL立即可用"的插件式体验!** ✨ \ No newline at end of file + return translateService.Translate(text, lang), nil + }) + +// SQL used +// SELECT translate(message, 'zh-CN') as chinese_message FROM stream +``` + +## 🏁 Summary + +StreamSQL's plugin-style custom function system enables you to: + +1. **🔌 Plug and Play** - Use the function immediately in the SQL after registering it +2. **🚀 Zero-downtime expansion** - Dynamically adds functionality at runtime +3. **🎯 Type Intelligence** - Automatically selects the optimal processing mode based on function type +4. **📈 Infinite Possibilities** - Supports arbitrarily complex business logic + +**truly delivers the plug-in experience of "write a function and use it immediately in SQL"!** ✨ diff --git a/examples/advanced-functions/README.md b/examples/advanced-functions/README.md index f21ca6f..dc43cdc 100644 --- a/examples/advanced-functions/README.md +++ b/examples/advanced-functions/README.md @@ -1,91 +1,91 @@ -# 高级自定义函数示例 - -## 简介 - -展示StreamSQL自定义函数系统的高级特性,包括状态管理、缓存机制、性能优化等。 - -## 功能演示 - -- 🏗️ **结构体方式实现**:完整的函数生命周期管理 -- 💾 **状态管理**:有状态函数的实现和使用 -- ⚡ **性能优化**:缓存机制和优化策略 -- 🛡️ **高级验证**:复杂参数验证和错误处理 -- 🧵 **线程安全**:并发环境下的安全实现 - -## 运行方式 - -```bash -cd examples/advanced-functions -go run main.go -``` - -## 代码亮点 - -### 1. 完整结构体实现 -```go -type AdvancedFunction struct { - *functions.BaseFunction +# Advanced custom function examples + +## Introduction + +Showcase advanced features of StreamSQL custom function systems, including state management, caching mechanisms, performance optimization, and more. + +## Feature Demonstration + +- 🏗️ **Struct-based implementation**: Complete function lifecycle management +- 💾 **State Management**: Implementation and use of stateful functions +- ⚡ **Performance Optimization**: Caching mechanisms and optimization strategies +- 🛡️ **Advanced Validation**: Complex parameter validation and error handling +- 🧵 **Thread Safety**: Secure implementation in concurrent environments + +## Operating Mode + +```bash +cd examples/advanced-functions +go run main.go +``` + +## Code Highlights + +### 1. Complete structural implementation +```go +type AdvancedFunction struct { + *functions.BaseFunction cache map[string]interface{} mutex sync.RWMutex counter int64 -} - -func (f *AdvancedFunction) Validate(args []interface{}) error { - // 自定义验证逻辑 - return f.ValidateArgCount(args) -} - -func (f *AdvancedFunction) Execute(ctx *functions.FunctionContext, args []interface{}) (interface{}, error) { - // 复杂的执行逻辑 -} -``` - -### 2. 状态管理 -```go -type StatefulFunction struct { - *functions.BaseFunction +} + +func (f *AdvancedFunction) Validate(args []interface{}) error { + // Custom verification logic + return f.ValidateArgCount(args) +} + +func (f *AdvancedFunction) Execute(ctx *functions.FunctionContext, args []interface{}) (interface{}, error) { + // Complex execution logic +} +``` + +### 2. Status management +```go +type StatefulFunction struct { + *functions.BaseFunction history []float64 - mutex sync.Mutex -} - -// 维护历史数据状态 -func (f *StatefulFunction) Execute(ctx *functions.FunctionContext, args []interface{}) (interface{}, error) { - f.mutex.Lock() - defer f.mutex.Unlock() - - // 更新状态 - f.history = append(f.history, value) - return f.calculate(), nil -} -``` - -### 3. 缓存优化 -```go -func (f *CachedFunction) Execute(ctx *functions.FunctionContext, args []interface{}) (interface{}, error) { - key := f.generateKey(args) - - // 检查缓存 - if result, exists := f.getFromCache(key); exists { - return result, nil - } - - // 计算并缓存 - result := f.compute(args) - f.setCache(key, result) - return result, nil -} -``` - -## 高级特性 - -- **内存管理**:合理的资源分配和释放 -- **错误恢复**:异常情况的处理和恢复 -- **性能监控**:执行时间和资源使用统计 -- **热重载**:运行时函数更新和替换 - -## 适用场景 - -- 🎯 **高性能应用**:需要极致性能优化的场景 -- 🔄 **状态跟踪**:需要维护历史状态的计算 -- 📈 **复杂算法**:机器学习、统计分析等 -- 🏢 **企业级系统**:生产环境的稳定性要求 \ No newline at end of file + mutex sync.Mutex +} + +// Maintain the status of historical data +func (f *StatefulFunction) Execute(ctx *functions.FunctionContext, args []interface{}) (interface{}, error) { + f.mutex.Lock() + defer f.mutex.Unlock() + + // Update status + f.history = append(f.history, value) + return f.calculate(), nil +} +``` + +### 3. Cache optimization +```go +func (f *CachedFunction) Execute(ctx *functions.FunctionContext, args []interface{}) (interface{}, error) { + key := f.generateKey(args) + + // Check the cache + if result, exists := f.getFromCache(key); exists { + return result, nil + } + + // Calculate and cache + result := f.compute(args) + f.setCache(key, result) + return result, nil +} +``` + +## Advanced Features + +- **Memory Management**: Reasonable resource allocation and allocation +- **Error Recovery**: Handling and restoring exceptions +- **Performance Monitoring**: Execution time and resource usage statistics +- **Hot-reloaded**: Runtime function updates and replacements + +## Applicable Scenarios + +- 🎯 **High-performance application**: Scenarios requiring extreme performance optimization +- 🔄 **Status Tracking**: Calculations that require maintenance of historical status +- 📈 **Complex algorithms**: machine learning, statistical analysis, etc +- 🏢 **Enterprise-level system**: Stability requirements for production environments diff --git a/examples/advanced-functions/main.go b/examples/advanced-functions/main.go index 92860a2..6f367ee 100644 --- a/examples/advanced-functions/main.go +++ b/examples/advanced-functions/main.go @@ -12,7 +12,7 @@ import ( func main() { fmt.Println("=== StreamSQL 高级函数示例 ===") - // 1. 注册自定义函数:温度华氏度转摄氏度 + // 1. Register a custom function: Temperature Fahrenheit to Celsius err := functions.RegisterCustomFunction("fahrenheit_to_celsius", functions.TypeCustom, "温度转换", "华氏度转摄氏度", 1, 1, func(ctx *functions.FunctionContext, args []any) (any, error) { fahrenheit, err := cast.ToFloat64E(args[0]) @@ -27,11 +27,11 @@ func main() { } fmt.Println("✓ 注册自定义函数:fahrenheit_to_celsius") - // 2. 创建 StreamSQL 实例 + // 2. Create a StreamSQL instance ssql := streamsql.New() defer ssql.Stop() - // 3. 定义包含高级函数的 SQL + // 3. Define SQL containing advanced functions sql := ` SELECT device, @@ -43,26 +43,26 @@ func main() { WITH (TIMESTAMP='ts', TIMEUNIT='ss') ` - // 4. 执行 SQL + // 4. Execute SQL err = ssql.Execute(sql) if err != nil { panic(fmt.Sprintf("执行SQL失败: %v", err)) } fmt.Println("✓ SQL执行成功") - // 5. 添加结果监听器 + // 5. Add a result listener ssql.AddSink(func(result []map[string]any) { fmt.Printf("📊 聚合结果: %v\n", result) }) - // 6. 模拟传感器数据 + // 6. Simulating sensor data baseTime := time.Now() sensorData := []map[string]any{ - {"device": "sensor1", "temperature": 68.0, "humidity": 25.0, "ts": baseTime.UnixMicro()}, // 20°C, 湿度25% - {"device": "sensor1", "temperature": 86.0, "humidity": 36.0, "ts": baseTime.Unix()}, // 30°C, 湿度36% - {"device": "sensor2", "temperature": 32.0, "humidity": 49.0, "ts": baseTime.Unix()}, // 0°C, 湿度49% - {"device": "sensor2", "temperature": 104.0, "humidity": 64.0, "ts": baseTime.Unix()}, // 40°C, 湿度64% - {"device": "temperature_probe", "temperature": 212.0, "humidity": 81.0, "ts": baseTime.Unix()}, // 100°C, 湿度81% + {"device": "sensor1", "temperature": 68.0, "humidity": 25.0, "ts": baseTime.UnixMicro()}, // 20°C, Humidity 25% + {"device": "sensor1", "temperature": 86.0, "humidity": 36.0, "ts": baseTime.Unix()}, // 30°C, Humidity 36% + {"device": "sensor2", "temperature": 32.0, "humidity": 49.0, "ts": baseTime.Unix()}, // 0°C, Humidity 49% + {"device": "sensor2", "temperature": 104.0, "humidity": 64.0, "ts": baseTime.Unix()}, // 40°C, Humidity 64% + {"device": "temperature_probe", "temperature": 212.0, "humidity": 81.0, "ts": baseTime.Unix()}, // 100°C, Humidity 81% } fmt.Println("\n🌡️ 发送传感器数据:") @@ -72,30 +72,30 @@ func main() { ssql.Emit(data) } - // 7. 等待处理完成 + // 7. Wait for processing to complete fmt.Println("\n⏳ 等待窗口处理...") time.Sleep(3 * time.Second) - // 8. 演示内置函数 + // 8. Demonstrate built-in functions fmt.Println("\n🔧 内置函数演示:") - // 数学函数 + // Mathematical functions fmt.Printf(" abs(-15.5) = %.1f\n", callFunction("abs", -15.5)) fmt.Printf(" sqrt(16) = %.1f\n", callFunction("sqrt", 16.0)) - // 字符串函数 + // String function fmt.Printf(" concat('Hello', ' ', 'World') = %s\n", callFunction("concat", "Hello", " ", "World")) fmt.Printf(" upper('streamsql') = %s\n", callFunction("upper", "streamsql")) fmt.Printf(" length('StreamSQL') = %d\n", callFunction("length", "StreamSQL")) - // 转换函数 + // Conversion function fmt.Printf(" hex2dec('ff') = %d\n", callFunction("hex2dec", "ff")) fmt.Printf(" dec2hex(255) = %s\n", callFunction("dec2hex", 255)) - // 时间函数 + // Time function fmt.Printf(" now() = %v\n", callFunction("now")) - // 9. 显示已注册的函数 + // 9. Displays registered functions fmt.Println("\n📋 已注册的函数:") allFunctions := functions.ListAll() for name, fn := range allFunctions { @@ -105,7 +105,7 @@ func main() { fmt.Println("\n✅ 示例完成!") } -// 辅助函数:调用函数并返回结果 +// Auxiliary function: calls the function and returns the result func callFunction(name string, args ...any) any { ctx := &functions.FunctionContext{ Data: make(map[string]any), diff --git a/examples/complex-nested-access/main.go b/examples/complex-nested-access/main.go index e3a98e1..66bd8ab 100644 --- a/examples/complex-nested-access/main.go +++ b/examples/complex-nested-access/main.go @@ -14,36 +14,36 @@ func main() { fmt.Println("🔧 StreamSQL 复杂嵌套字段访问功能演示") fmt.Println("=======================================") - // 创建 StreamSQL 实例 + // Create a StreamSQL instance ssql := streamsql.New() defer ssql.Stop() - // 演示1: 数组索引访问 + // Demo 1: Array index access fmt.Println("\n📊 演示1: 数组索引访问") demonstrateArrayAccess(ssql) - // 演示2: Map键访问 + // Demo 2: Map key access fmt.Println("\n🗝️ 演示2: Map键访问") demonstrateMapKeyAccess(ssql) - // 演示3: 混合复杂访问 + // Demo 3: Hybrid Complex Access fmt.Println("\n🔄 演示3: 混合复杂访问") demonstrateComplexMixedAccess(ssql) - // 演示4: 负数索引访问 + // Demo 4: Negative Index Access fmt.Println("\n⬅️ 演示4: 负数索引访问") demonstrateNegativeIndexAccess(ssql) - // 演示5: 数组索引聚合计算 + // Demo 5: Array Index Aggregation Computation fmt.Println("\n📈 演示5: 数组索引聚合计算") demonstrateArrayIndexAggregation(ssql) fmt.Println("\n✅ 演示完成!") } -// 演示数组索引访问 +// Demonstration of array index access func demonstrateArrayAccess(ssql *streamsql.Streamsql) { - // SQL查询:提取数组中的特定元素 + // SQL query: extracting specific elements from an array rsql := `SELECT device, sensors[0].temperature as first_sensor_temp, sensors[1].humidity as second_sensor_humidity, @@ -56,7 +56,7 @@ func demonstrateArrayAccess(ssql *streamsql.Streamsql) { return } - // 准备测试数据 + // Prepare test data testData := []map[string]any{ { "device": "工业传感器-001", @@ -82,7 +82,7 @@ func demonstrateArrayAccess(ssql *streamsql.Streamsql) { var wg sync.WaitGroup wg.Add(1) - // 设置结果回调 + // Set result callback ssql.AddSink(func(result []map[string]any) { defer wg.Done() @@ -97,18 +97,18 @@ func demonstrateArrayAccess(ssql *streamsql.Streamsql) { } }) - // 添加测试数据 + // Add test data for _, data := range testData { ssql.Emit(data) } - // 等待结果 + // Wait for the results wg.Wait() } -// 演示Map键访问 +// Demonstration of Map key access func demonstrateMapKeyAccess(ssql *streamsql.Streamsql) { - // SQL查询:使用字符串键访问Map数据 + // SQL query: Accessing Map data using string keys rsql := `SELECT device_id, config['host'] as server_host, config["port"] as server_port, @@ -122,7 +122,7 @@ func demonstrateMapKeyAccess(ssql *streamsql.Streamsql) { return } - // 准备测试数据 + // Prepare test data testData := []map[string]any{ { "device_id": "gateway-001", @@ -165,7 +165,7 @@ func demonstrateMapKeyAccess(ssql *streamsql.Streamsql) { var wg sync.WaitGroup wg.Add(1) - // 设置结果回调 + // Set result callback ssql.AddSink(func(result []map[string]any) { defer wg.Done() @@ -182,18 +182,18 @@ func demonstrateMapKeyAccess(ssql *streamsql.Streamsql) { } }) - // 添加测试数据 + // Add test data for _, data := range testData { ssql.Emit(data) } - // 等待结果 + // Wait for the results wg.Wait() } -// 演示混合复杂访问 +// Demonstration of hybrid complex access func demonstrateComplexMixedAccess(ssql *streamsql.Streamsql) { - // SQL查询:混合使用数组索引、Map键和嵌套字段访问 + // SQL queries: Combining array indexes, Map keys, and nested field access rsql := `SELECT building, floors[0].rooms[2]['name'] as first_floor_room3_name, floors[1].sensors[0].readings['temperature'] as second_floor_first_sensor_temp, @@ -207,11 +207,11 @@ func demonstrateComplexMixedAccess(ssql *streamsql.Streamsql) { return } - // 准备复杂嵌套数据 + // Prepare complex nested data testData := map[string]any{ "building": "智能大厦A座", "floors": []any{ - // 第一层 + // The first level map[string]any{ "floor_number": 1, "rooms": []any{ @@ -221,7 +221,7 @@ func demonstrateComplexMixedAccess(ssql *streamsql.Streamsql) { map[string]any{"name": "休息区", "type": "lounge"}, }, }, - // 第二层 + // The second floor map[string]any{ "floor_number": 2, "sensors": []any{ @@ -262,7 +262,7 @@ func demonstrateComplexMixedAccess(ssql *streamsql.Streamsql) { var wg sync.WaitGroup wg.Add(1) - // 设置结果回调 + // Set result callback ssql.AddSink(func(result []map[string]any) { defer wg.Done() @@ -279,16 +279,16 @@ func demonstrateComplexMixedAccess(ssql *streamsql.Streamsql) { } }) - // 添加数据 + // Add data ssql.Emit(testData) - // 等待结果 + // Wait for the results wg.Wait() } -// 演示负数索引访问 +// Demonstrating negative index access func demonstrateNegativeIndexAccess(ssql *streamsql.Streamsql) { - // SQL查询:使用负数索引访问数组末尾元素 + // SQL query: Uses negative indexes to access the last element of an array rsql := `SELECT device_name, readings[-1] as latest_reading, history[-2] as second_last_event, @@ -301,7 +301,7 @@ func demonstrateNegativeIndexAccess(ssql *streamsql.Streamsql) { return } - // 准备测试数据 + // Prepare test data testData := []map[string]any{ { "device_name": "温度监测器-Alpha", @@ -320,7 +320,7 @@ func demonstrateNegativeIndexAccess(ssql *streamsql.Streamsql) { var wg sync.WaitGroup wg.Add(1) - // 设置结果回调 + // Set result callback ssql.AddSink(func(result []map[string]any) { defer wg.Done() @@ -336,18 +336,18 @@ func demonstrateNegativeIndexAccess(ssql *streamsql.Streamsql) { } }) - // 添加测试数据 + // Add test data for _, data := range testData { ssql.Emit(data) } - // 等待结果 + // Wait for the results wg.Wait() } -// 演示数组索引聚合计算 +// Demonstration of array index aggregation calculation func demonstrateArrayIndexAggregation(ssql *streamsql.Streamsql) { - // SQL查询:对数组中特定位置的数据进行聚合计算 + // SQL Query: Aggregates data at specific locations in an array for calculation rsql := `SELECT location, AVG(sensors[0].temperature) as avg_first_sensor_temp, MAX(sensors[1].humidity) as max_second_sensor_humidity, @@ -366,7 +366,7 @@ func demonstrateArrayIndexAggregation(ssql *streamsql.Streamsql) { var wg sync.WaitGroup wg.Add(1) - // 设置结果回调 + // Set result callback ssql.AddSink(func(result []map[string]any) { defer wg.Done() @@ -383,7 +383,7 @@ func demonstrateArrayIndexAggregation(ssql *streamsql.Streamsql) { } }) - // 生成模拟数据 + // Generate simulated data locations := []string{"车间A", "车间B", "车间C"} go func() { @@ -407,11 +407,11 @@ func demonstrateArrayIndexAggregation(ssql *streamsql.Streamsql) { } ssql.Emit(data) - time.Sleep(200 * time.Millisecond) // 每200ms发送一条数据 + time.Sleep(200 * time.Millisecond) // Send a data piece every 200ms } }() - // 等待聚合结果 + // Waiting for the aggregated results ctx, cancel := context.WithTimeout(context.Background(), 8*time.Second) defer cancel() diff --git a/examples/comprehensive-test/README.md b/examples/comprehensive-test/README.md index 08c2306..c3adaf3 100644 --- a/examples/comprehensive-test/README.md +++ b/examples/comprehensive-test/README.md @@ -1,92 +1,92 @@ -# StreamSQL 综合测试演示 +# StreamSQL Comprehensive test demonstration -这个示例提供了一个统一的入口来测试和验证StreamSQL的各种功能特性。 +This example provides a unified entry point to test and validate various functional features of StreamSQL. -## 功能覆盖 +## Feature Override -### 1. 基础数据过滤 -- 简单的WHERE条件过滤 -- 实时数据流处理 -- 结果回调处理 +### 1. Basic data filtering +- Simple WHERE conditional filtering +- Real-time data stream processing +- Result callback processing -### 2. 聚合分析 -- 滚动窗口聚合(TumblingWindow) -- 多种聚合函数:AVG、COUNT、MAX、MIN -- 按字段分组 +### 2. Aggregate analysis +- Scrolling window aggregation (TumblingWindow) +- Multiple aggregate functions: AVG, COUNT, MAX, MIN +- Grouping by field -### 3. 滑动窗口 -- 滑动窗口分析(SlidingWindow) -- 窗口大小和滑动间隔配置 -- 连续数据流处理 +### 3. Sliding window +- Sliding window analysis (SlidingWindow) +- Window size and sliding spacing configuration +- Continuous data stream processing -### 4. 嵌套字段访问 -- 多层嵌套对象访问 -- 复杂数据结构处理 -- 嵌套字段条件过滤 +### 4. Nested field access +- Multi-layer nested object access +- Handling complex data structures +- Nested field conditional filtering -### 5. 自定义函数 -- 数学函数(square、circle_area) -- 转换函数(f_to_c) -- 函数注册和使用 +### 5. Custom functions +- Mathematical functions (square, circle_area) +- Transformation Function (f_to_c) +- Function registration and usage -### 6. 复杂查询 -- 多种功能组合使用 -- 嵌套字段 + 自定义函数 + 聚合 -- 复杂业务场景模拟 +### 6. Complex queries +- Multiple feature combinations +- Nested fields + custom functions + aggregation +- Complex business scenario simulation -## 运行方式 +## Operating Mode ```bash cd examples\comprehensive-test go run main.go ``` -## 预期输出 +## Expected Output -程序会依次执行6个测试场景,每个场景都会输出相应的结果: +The program will sequentially execute six test scenarios, each producing corresponding results: -1. **基础过滤测试**:显示温度大于25度的设备告警 -2. **聚合分析测试**:显示每个设备的温度统计信息 -3. **滑动窗口测试**:显示滑动窗口内的温度分析 -4. **嵌套字段测试**:显示复杂数据结构的字段提取 -5. **自定义函数测试**:显示自定义函数的计算结果 -6. **复杂查询测试**:显示综合功能的查询结果 +1. **Basic Filtration Test**: Displays alarms for equipment with temperatures above 25°C +2. **Aggregate Analysis Test**: Displays temperature statistics for each device +3. **Sliding Window Test**: Displays temperature analysis inside the sliding window +4. **Nested Field Testing**: Displays field extraction for complex data structures +5. **Custom Function Test**: Displays the calculation results of the custom function +6. **Complex Query Test**: Displays query results for comprehensive functions -## 测试数据 +## Test Data -- **传感器数据**:包含设备ID、温度、湿度等信息 -- **嵌套结构**:设备信息、位置信息、传感器数据的多层嵌套 -- **随机数据**:使用随机数生成模拟真实的传感器数据流 +- **Sensor Data**: Contains information such as device ID, temperature, humidity, etc +- **Nested Structure**: Multi-layer nesting of device information, location information, and sensor data +- **Random Data**: Generates simulated real sensor data streams using random numbers -## 自定义函数说明 +## Custom function description ### square(x) -- **功能**:计算数值的平方 -- **参数**:数值 -- **返回**:平方值 +- **Function**: Calculate the square of a value +- **Parameter**: Numeric value +- **Returns**: squared value ### f_to_c(fahrenheit) -- **功能**:华氏度转摄氏度 -- **参数**:华氏度温度值 -- **返回**:摄氏度温度值 -- **公式**:(F - 32) × 5/9 +- **Function**: Fahrenheit to Celsius +- **Parameter**: Fahrenheit temperature value +- **Return**: Celsius temperature value +- **Formula**:(F - 32) × 5/9 ### circle_area(radius) -- **功能**:计算圆的面积 -- **参数**:半径 -- **返回**:圆的面积 -- **公式**:π × r² +- **Function**: Calculate the area of a circle +- **Parameter**: Radius +- **Return**: Area of the circle +- **Formula**: π × r² -## 注意事项 +## Notes -1. **窗口触发**:聚合查询需要等待窗口时间到达或手动触发 -2. **数据格式**:确保输入数据格式正确,特别是嵌套字段的结构 -3. **函数注册**:自定义函数需要在使用前注册 -4. **资源清理**:使用defer确保StreamSQL实例正确关闭 +1. **Window Trigger**: Aggregated queries need to wait for window time to arrive or to trigger manually +2. **Data Format**: Ensure the input data format is correct, especially the structure of nested fields +3. **Function Registration**: Custom functions need to be registered before use +4. **Resource Cleanup**: Use defer to ensure the StreamSQL instance closes correctly -## 扩展建议 +## Extended Suggestions -- 可以添加更多的自定义函数 -- 可以测试更复杂的窗口配置 -- 可以添加错误处理和异常数据测试 -- 可以集成性能测试和压力测试 \ No newline at end of file +- More custom functions can be added +- More complex window configurations can be tested +- Error handling and anomaly data testing can be added +- Performance testing and stress testing can be integrated diff --git a/examples/comprehensive-test/main.go b/examples/comprehensive-test/main.go index caa3ba7..e1588a5 100644 --- a/examples/comprehensive-test/main.go +++ b/examples/comprehensive-test/main.go @@ -15,20 +15,20 @@ func main() { fmt.Println("🚀 StreamSQL 综合测试演示") fmt.Println("=============================") - // 注册自定义函数 + // Register custom functions registerCustomFunctions() - // 运行各种测试场景 + // Run various test scenarios runAllTests() fmt.Println("\n✅ 所有测试完成!") } -// 注册自定义函数 +// Register custom functions func registerCustomFunctions() { fmt.Println("\n📋 注册自定义函数...") - // 数学函数:平方 + // Mathematical function: square err := functions.RegisterCustomFunction( "square", functions.TypeMath, @@ -46,7 +46,7 @@ func registerCustomFunctions() { fmt.Println(" ✓ 注册数学函数: square") } - // 华氏度转摄氏度函数 + // Fahrenheit to Celsius function err = functions.RegisterCustomFunction( "f_to_c", functions.TypeConversion, @@ -65,7 +65,7 @@ func registerCustomFunctions() { fmt.Println(" ✓ 注册转换函数: f_to_c") } - // 圆面积计算函数 + // Circle area calculation function err = functions.RegisterCustomFunction( "circle_area", functions.TypeMath, @@ -88,28 +88,28 @@ func registerCustomFunctions() { } } -// 运行所有测试 +// Run all tests func runAllTests() { - // 测试1:基础数据过滤 + // Test 1: Basic data filtering testBasicFiltering() - // 测试2:聚合分析 + // Test 2: Aggregate analysis testAggregation() - // 测试3:滑动窗口 + // Test 3: Sliding windows testSlidingWindow() - // 测试4:嵌套字段访问 + // Test 4: Nested field access testNestedFields() - // 测试5:自定义函数 + // Test 5: Custom functions testCustomFunctions() - // 测试6:复杂查询 + // Test 6: Complex queries testComplexQuery() } -// 测试1:基础数据过滤 +// Test 1: Basic data filtering func testBasicFiltering() { fmt.Println("\n🔍 测试1:基础数据过滤") fmt.Println("========================") @@ -117,7 +117,7 @@ func testBasicFiltering() { ssql := streamsql.New() defer ssql.Stop() - // 过滤温度大于25度的数据 + // Filter data with temperatures above 25 degrees sql := "SELECT deviceId, temperature FROM stream WHERE temperature > 25" err := ssql.Execute(sql) @@ -126,17 +126,17 @@ func testBasicFiltering() { return } - // 添加结果处理函数 + // Add a result processing function ssql.AddSink(func(result []map[string]any) { fmt.Printf(" 📊 高温告警: %v\n", result) }) - // 发送测试数据 + // Send test data testData := []map[string]any{ - {"deviceId": "sensor001", "temperature": 23.5}, // 不会触发告警 - {"deviceId": "sensor002", "temperature": 28.3}, // 会触发告警 - {"deviceId": "sensor003", "temperature": 31.2}, // 会触发告警 - {"deviceId": "sensor004", "temperature": 22.1}, // 不会触发告警 + {"deviceId": "sensor001", "temperature": 23.5}, // No alarms will be triggered + {"deviceId": "sensor002", "temperature": 28.3}, // An alarm will be triggered + {"deviceId": "sensor003", "temperature": 31.2}, // An alarm will be triggered + {"deviceId": "sensor004", "temperature": 22.1}, // No alarms will be triggered } for _, data := range testData { @@ -148,7 +148,7 @@ func testBasicFiltering() { fmt.Println(" ✅ 基础过滤测试完成") } -// 测试2:聚合分析 +// Test 2: Aggregate analysis func testAggregation() { fmt.Println("\n📈 测试2:聚合分析") fmt.Println("==================") @@ -156,7 +156,7 @@ func testAggregation() { ssql := streamsql.New() defer ssql.Stop() - // 每2秒计算一次各设备的平均温度 + // Calculate the average temperature of each device every 2 seconds sql := `SELECT deviceId, AVG(temperature) as avg_temp, COUNT(*) as sample_count, @@ -171,18 +171,18 @@ func testAggregation() { return } - // 处理聚合结果 + // Process the aggregated results ssql.AddSink(func(result []map[string]any) { fmt.Printf(" 📊 聚合结果: %v\n", result) }) - // 模拟传感器数据流 + // Simulating sensor data streams devices := []string{"sensor001", "sensor002", "sensor003"} for i := 0; i < 8; i++ { for _, device := range devices { data := map[string]any{ "deviceId": device, - "temperature": 20.0 + rand.Float64()*15, // 20-35度随机温度 + "temperature": 20.0 + rand.Float64()*15, // Random temperature of 20-35 degrees "timestamp": time.Now(), } ssql.Emit(data) @@ -190,14 +190,14 @@ func testAggregation() { time.Sleep(300 * time.Millisecond) } - // 等待窗口触发 + // Wait for the window to trigger time.Sleep(2 * time.Second) ssql.Stream().Window.Trigger() time.Sleep(500 * time.Millisecond) fmt.Println(" ✅ 聚合分析测试完成") } -// 测试3:滑动窗口 +// Test 3: Sliding windows func testSlidingWindow() { fmt.Println("\n🔄 测试3:滑动窗口") fmt.Println("==================") @@ -205,7 +205,7 @@ func testSlidingWindow() { ssql := streamsql.New() defer ssql.Stop() - // 6秒滑动窗口,每2秒滑动一次 + // Swipe the window every 6 seconds, then every 2 seconds sql := `SELECT deviceId, AVG(temperature) as avg_temp, MAX(temperature) as max_temp, @@ -225,7 +225,7 @@ func testSlidingWindow() { fmt.Printf(" 📊 滑动窗口分析: %v\n", result) }) - // 持续发送数据 + // Continuously transmitting data for i := 0; i < 10; i++ { data := map[string]any{ "deviceId": "sensor001", @@ -240,7 +240,7 @@ func testSlidingWindow() { fmt.Println(" ✅ 滑动窗口测试完成") } -// 测试4:嵌套字段访问 +// Test 4: Nested field access func testNestedFields() { fmt.Println("\n🔧 测试4:嵌套字段访问") fmt.Println("=======================") @@ -248,7 +248,7 @@ func testNestedFields() { ssql := streamsql.New() defer ssql.Stop() - // 访问嵌套字段的SQL查询 + // SQL queries accessing nested fields sql := `SELECT device.info.name as device_name, device.location.building as building, sensor.temperature as temp, @@ -266,7 +266,7 @@ func testNestedFields() { fmt.Printf(" 📊 嵌套字段结果: %v\n", result) }) - // 发送嵌套结构数据 + // Send nested structure data complexData := []map[string]any{ { "device": map[string]any{ @@ -290,7 +290,7 @@ func testNestedFields() { "info": map[string]any{ "name": "湿度传感器002", "type": "humidity", - "status": "inactive", // 不会匹配 + "status": "inactive", // It won't match }, "location": map[string]any{ "building": "B栋", @@ -313,7 +313,7 @@ func testNestedFields() { fmt.Println(" ✅ 嵌套字段测试完成") } -// 测试5:自定义函数 +// Test 5: Custom functions func testCustomFunctions() { fmt.Println("\n🎯 测试5:自定义函数") fmt.Println("====================") @@ -321,7 +321,7 @@ func testCustomFunctions() { ssql := streamsql.New() defer ssql.Stop() - // 使用自定义函数的SQL查询 + // SQL queries using custom functions sql := `SELECT device, square(value) as squared_value, @@ -340,23 +340,23 @@ func testCustomFunctions() { fmt.Printf(" 📊 自定义函数结果: %v\n", result) }) - // 添加测试数据 + // Add test data testData := []map[string]any{ { "device": "sensor1", "value": 5.0, - "temperature": 68.0, // 华氏度 + "temperature": 68.0, // The degree of the Fahrenheit degree "radius": 3.0, }, { "device": "sensor2", "value": 10.0, - "temperature": 86.0, // 华氏度 + "temperature": 86.0, // The degree of the Fahrenheit degree "radius": 2.5, }, { "device": "sensor3", - "value": 0.0, // 不会匹配WHERE条件 + "value": 0.0, // It will not match the WHERE condition "temperature": 32.0, "radius": 1.0, }, @@ -371,7 +371,7 @@ func testCustomFunctions() { fmt.Println(" ✅ 自定义函数测试完成") } -// 测试6:复杂查询 +// Test 6: Complex queries func testComplexQuery() { fmt.Println("\n🔬 测试6:复杂查询") fmt.Println("==================") @@ -379,7 +379,7 @@ func testComplexQuery() { ssql := streamsql.New() defer ssql.Stop() - // 复杂的聚合查询,结合自定义函数和嵌套字段 + // Complex aggregated queries, combined with custom functions and nested fields sql := `SELECT device.location as location, AVG(square(sensor.temperature)) as avg_temp_squared, @@ -400,7 +400,7 @@ func testComplexQuery() { fmt.Printf(" 📊 复杂查询结果: %v\n", result) }) - // 发送复杂测试数据 + // Send complex test data locations := []string{"room-A", "room-B", "room-C"} for i := 0; i < 12; i++ { location := locations[i%len(locations)] @@ -408,10 +408,10 @@ func testComplexQuery() { "device": map[string]any{ "location": location, "status": "online", - "radius": 1.0 + rand.Float64()*2.0, // 1-3的随机半径 + "radius": 1.0 + rand.Float64()*2.0, // Random radius of 1-3 }, "sensor": map[string]any{ - "temperature": 25.0 + rand.Float64()*10.0, // 25-35度 + "temperature": 25.0 + rand.Float64()*10.0, // 25-35 degrees "humidity": 50.0 + rand.Float64()*30.0, // 50-80% }, "timestamp": time.Now(), @@ -420,7 +420,7 @@ func testComplexQuery() { time.Sleep(300 * time.Millisecond) } - // 等待窗口触发 + // Wait for the window to trigger time.Sleep(3 * time.Second) ssql.Stream().Window.Trigger() time.Sleep(500 * time.Millisecond) diff --git a/examples/custom-functions-demo/README.md b/examples/custom-functions-demo/README.md index 58fdf96..53c6c3a 100644 --- a/examples/custom-functions-demo/README.md +++ b/examples/custom-functions-demo/README.md @@ -1,71 +1,71 @@ -# 自定义函数完整演示 - -## 简介 - -这是StreamSQL自定义函数系统的完整功能演示,涵盖了所有函数类型和高级用法。 - -## 功能演示 - -- 🔢 **数学函数**:距离计算、温度转换、圆面积计算 -- 📝 **字符串函数**:JSON提取、字符串反转、字符串重复 -- 🔄 **转换函数**:IP地址转换、字节大小格式化 -- 📅 **时间日期函数**:时间格式化、时间差计算 -- 📊 **聚合函数**:几何平均数、众数计算 -- 🔍 **分析函数**:移动平均值 -- 🛠️ **函数管理**:注册、查询、分类、注销 - -## 运行方式 - -```bash -cd examples/custom-functions-demo -go run main.go -``` - -## 代码亮点 - -### 1. 完整函数类型覆盖 -```go -// 数学函数:距离计算 -functions.RegisterCustomFunction("distance", functions.TypeMath, ...) - -// 字符串函数:JSON提取 -functions.RegisterCustomFunction("json_extract", functions.TypeString, ...) - -// 转换函数:IP转换 -functions.RegisterCustomFunction("ip_to_int", functions.TypeConversion, ...) -``` - -### 2. 自定义聚合函数 -```go -type GeometricMeanFunction struct { - *functions.BaseFunction -} - -// 配合聚合器使用 -aggregator.Register("geometric_mean", func() aggregator.AggregatorFunction { - return &GeometricMeanAggregator{} -}) -``` - -### 3. 复杂SQL查询 -```sql -SELECT - device, +# Complete demonstration of custom functions + +## Introduction + +This is a complete demonstration of the StreamSQL custom function system, covering all function types and advanced usages. + +## Feature Demonstration + +- 🔢 **Mathematical Functions**: Distance Calculation, Temperature Conversion, Circle Area Calculation +- 📝 **String function**: JSON extract, invert string, repeat string +- 🔄 **Conversion function**: IP Address translation and byte size formatting +- 📅 **Time-Date Function**: Time formatting, time difference calculation +- 📊 **Aggregate function**: Geometric mean and mode calculations +- 🔍 **Analysis Function**: Moving Average +- 🛠️ **Function Management**: Register, query, classify, and deregister + +## Operating Mode + +```bash +cd examples/custom-functions-demo +go run main.go +``` + +## Code Highlights + +### 1. Full function type coverage +```go +// Mathematical function: distance calculation +functions.RegisterCustomFunction("distance", functions.TypeMath, ...) + +// String function: JSON extraction +functions.RegisterCustomFunction("json_extract", functions.TypeString, ...) + +// Conversion function: IP conversion +functions.RegisterCustomFunction("ip_to_int", functions.TypeConversion, ...) +``` + +### 2. Custom aggregation functions +```go +type GeometricMeanFunction struct { + *functions.BaseFunction +} + +// Used together with an aggregator +aggregator.Register("geometric_mean", func() aggregator.AggregatorFunction { + return &GeometricMeanAggregator{} +}) +``` + +### 3. Complex SQL queries +```sql +SELECT + device, AVG(distance(x1, y1, x2, y2)) as avg_distance, json_extract(metadata, 'version') as version, format_bytes(memory_usage) as formatted_memory FROM stream -GROUP BY device, TumblingWindow('1s') -``` - -## 演示流程 - -1. **函数注册阶段** - 注册各类型函数 -2. **SQL测试阶段** - 在不同模式下测试函数 -3. **管理功能演示** - 展示函数发现和管理功能 - -## 适用场景 - -- 🏢 **企业级应用**:了解完整功能特性 -- 🔬 **功能验证**:测试复杂函数组合 -- �� **学习参考**:最佳实践和使用模式 \ No newline at end of file +GROUP BY device, TumblingWindow('1s') +``` + +## Demonstration Process + +1. **Function registration stage** - Register various types of functions +2. **SQL Testing Phase** - Test functions in different modes +3. **Management Feature Demonstration** - Showcase function discovery and management features + +## Applicable Scenarios + +- 🏢 **Enterprise-level application**: Understand the full range of features +- 🔬 **Functional Verification**: Test complex function combinations +- **Learning Reference**: Best practices and usage patterns diff --git a/examples/custom-functions-demo/main.go b/examples/custom-functions-demo/main.go index 7511b0b..4f9c7a4 100644 --- a/examples/custom-functions-demo/main.go +++ b/examples/custom-functions-demo/main.go @@ -18,46 +18,46 @@ func main() { fmt.Println("🚀 StreamSQL 自定义函数完整演示") fmt.Println("=======================================") - // 注册各种类型的自定义函数 + // Register various types of custom functions registerCustomFunctions() - // 演示自定义函数在SQL中的使用 + // Demonstrating the use of custom functions in SQL demonstrateCustomFunctions() - // 展示函数管理功能 + // Showcase function management features demonstrateFunctionManagement() fmt.Println("\n✅ 演示完成!") } -// 注册各种类型的自定义函数 +// Register various types of custom functions func registerCustomFunctions() { fmt.Println("\n📋 注册自定义函数...") - // 1. 注册数学函数 + // 1. Register for mathematical functions registerMathFunctions() - // 2. 注册字符串函数 + // 2. Register the string function registerStringFunctions() - // 3. 注册转换函数 + // 3. Register the conversion function registerConversionFunctions() - // 4. 注册时间日期函数 + // 4. Registration time and date function registerDateTimeFunctions() - // 5. 注册聚合函数 + // 5. Register the aggregation function registerAggregateFunctions() - // 6. 注册分析函数 + // 6. Register the analysis function registerAnalyticalFunctions() fmt.Println("✅ 所有自定义函数注册完成") } -// 注册数学函数 +// Register the math function func registerMathFunctions() { - // 距离计算函数 + // Distance calculation function err := functions.RegisterCustomFunction( "distance", functions.TypeMath, @@ -76,7 +76,7 @@ func registerMathFunctions() { ) checkError("注册distance函数", err) - // 华氏度转摄氏度函数 + // Fahrenheit to Celsius function err = functions.RegisterCustomFunction( "fahrenheit_to_celsius", functions.TypeMath, @@ -91,7 +91,7 @@ func registerMathFunctions() { ) checkError("注册fahrenheit_to_celsius函数", err) - // 圆面积计算函数 + // Circle area calculation function err = functions.RegisterCustomFunction( "circle_area", functions.TypeMath, @@ -112,9 +112,9 @@ func registerMathFunctions() { fmt.Println(" ✓ 数学函数: distance, fahrenheit_to_celsius, circle_area") } -// 注册字符串函数 +// Register string functions func registerStringFunctions() { - // JSON提取函数 + // JSON extraction function err := functions.RegisterCustomFunction( "json_extract", functions.TypeString, @@ -141,7 +141,7 @@ func registerStringFunctions() { ) checkError("注册json_extract函数", err) - // 字符串反转函数 + // String inversion function err = functions.RegisterCustomFunction( "reverse_string", functions.TypeString, @@ -161,7 +161,7 @@ func registerStringFunctions() { ) checkError("注册reverse_string函数", err) - // 字符串重复函数 + // String repeat function err = functions.RegisterCustomFunction( "repeat_string", functions.TypeString, @@ -190,9 +190,9 @@ func registerStringFunctions() { fmt.Println(" ✓ 字符串函数: json_extract, reverse_string, repeat_string") } -// 注册转换函数 +// Register the conversion function func registerConversionFunctions() { - // IP地址转整数函数 + // IP address conversion function to integer err := functions.RegisterCustomFunction( "ip_to_int", functions.TypeConversion, @@ -217,7 +217,7 @@ func registerConversionFunctions() { ) checkError("注册ip_to_int函数", err) - // 字节大小格式化函数 + // Byte size formatting function err = functions.RegisterCustomFunction( "format_bytes", functions.TypeConversion, @@ -242,9 +242,9 @@ func registerConversionFunctions() { fmt.Println(" ✓ 转换函数: ip_to_int, format_bytes") } -// 注册时间日期函数 +// Register the time-date function func registerDateTimeFunctions() { - // 时间格式化函数 + // Time formatting function err := functions.RegisterCustomFunction( "date_format", functions.TypeDateTime, @@ -272,7 +272,7 @@ func registerDateTimeFunctions() { ) checkError("注册date_format函数", err) - // 时间差计算函数 + // Time difference calculation function err = functions.RegisterCustomFunction( "time_diff", functions.TypeDateTime, @@ -293,17 +293,17 @@ func registerDateTimeFunctions() { fmt.Println(" ✓ 时间日期函数: date_format, time_diff") } -// 注册聚合函数 +// Register the aggregation function func registerAggregateFunctions() { - // 注册几何平均数聚合函数到functions模块 + // Register the geometric mean aggregation function into the functions module functions.Register(NewGeometricMeanFunction()) functions.RegisterAggregatorAdapter("geometric_mean") - // 注册众数聚合函数到functions模块 + // Register the mode aggregation function into the functions module functions.Register(NewModeFunction()) functions.RegisterAggregatorAdapter("mode_agg") - // 保留原有的aggregator注册用于兼容性 + // Keep the original aggregator registration for compatibility aggregator.Register("geometric_mean", func() aggregator.AggregatorFunction { return &GeometricMeanAggregator{} }) @@ -314,9 +314,9 @@ func registerAggregateFunctions() { fmt.Println(" ✓ 聚合函数: geometric_mean, mode_agg") } -// 注册分析函数 +// Register the analysis function func registerAnalyticalFunctions() { - // 移动平均函数 + // Moving average function err := functions.RegisterCustomFunction( "moving_avg", functions.TypeAnalytical, @@ -324,14 +324,14 @@ func registerAnalyticalFunctions() { "计算移动平均值", 2, 2, func(ctx *functions.FunctionContext, args []any) (any, error) { - // 这个函数需要状态管理,实际实现会比较复杂 - // 这里只是一个示例 + // This function requires state management, so its implementation is quite complex + // Here is just an example current := cast.ToFloat64(args[0]) window := cast.ToInt64(args[1]) - // 简化实现:直接返回当前值 - // 实际实现需要维护历史数据窗口 + // Simplified implementation: Returns the current value directly + // Actual implementation requires maintaining the historical data window _ = window return current, nil }, @@ -341,7 +341,7 @@ func registerAnalyticalFunctions() { fmt.Println(" ✓ 分析函数: moving_avg") } -// 几何平均数聚合函数 +// Geometric mean aggregation function type GeometricMeanFunction struct { *functions.BaseFunction product float64 @@ -365,7 +365,7 @@ func (f *GeometricMeanFunction) Validate(args []any) error { } func (f *GeometricMeanFunction) Execute(ctx *functions.FunctionContext, args []any) (any, error) { - // 批量执行模式 + // Batch execution mode product := 1.0 for _, arg := range args { val := cast.ToFloat64(arg) @@ -379,7 +379,7 @@ func (f *GeometricMeanFunction) Execute(ctx *functions.FunctionContext, args []a return math.Pow(product, 1.0/float64(len(args))), nil } -// 实现AggregatorFunction接口以支持增量计算 +// Implement the AggregatorFunction interface to support incremental computation func (f *GeometricMeanFunction) New() functions.AggregatorFunction { return &GeometricMeanFunction{ BaseFunction: f.BaseFunction, @@ -416,7 +416,7 @@ func (f *GeometricMeanFunction) Clone() functions.AggregatorFunction { } } -// 几何平均数聚合器(保留用于兼容性) +// Geometric Mean Aggregator (reserved for compatibility) type GeometricMeanAggregator struct { values []float64 } @@ -446,7 +446,7 @@ func (g *GeometricMeanAggregator) Result() any { return math.Pow(product, 1.0/float64(len(g.values))) } -// 众数聚合函数 +// Complex number aggregation function type ModeFunction struct { *functions.BaseFunction counts map[string]int @@ -470,7 +470,7 @@ func (f *ModeFunction) Validate(args []any) error { } func (f *ModeFunction) Execute(ctx *functions.FunctionContext, args []any) (any, error) { - // 批量执行模式 + // Batch execution mode counts := make(map[string]int) for _, arg := range args { key := fmt.Sprintf("%v", arg) @@ -492,7 +492,7 @@ func (f *ModeFunction) Execute(ctx *functions.FunctionContext, args []any) (any, return mode, nil } -// 实现AggregatorFunction接口以支持增量计算 +// Implement the AggregatorFunction interface to support incremental computation func (f *ModeFunction) New() functions.AggregatorFunction { return &ModeFunction{ BaseFunction: f.BaseFunction, @@ -536,7 +536,7 @@ func (f *ModeFunction) Clone() functions.AggregatorFunction { return clone } -// 众数聚合器(保留用于兼容性) +// Mode aggregator (reserved for compatibility) type ModeAggregator struct { counts map[string]int } @@ -568,7 +568,7 @@ func (m *ModeAggregator) Result() any { return mode } -// 演示自定义函数在SQL中的使用 +// Demonstrating the use of custom functions in SQL func demonstrateCustomFunctions() { fmt.Println("\n🎯 演示自定义函数在SQL中的使用") fmt.Println("================================") @@ -576,16 +576,16 @@ func demonstrateCustomFunctions() { ssql := streamsql.New() defer ssql.Stop() - // 测试数学函数 + // Test mathematical functions testMathFunctions(ssql) - // 测试字符串函数 + // Test string function testStringFunctions(ssql) - // 测试转换函数 + // Test the conversion function testConversionFunctions(ssql) - // 测试聚合函数 + // Test the aggregation function testAggregateFunctions(ssql) } @@ -608,23 +608,23 @@ func testMathFunctions(ssql *streamsql.Streamsql) { return } - // 添加测试数据 + // Add test data testData := []map[string]any{ { "device": "sensor1", - "temperature": 68.0, // 华氏度 + "temperature": 68.0, // The degree of the Fahrenheit degree "radius": 5.0, - "x1": 0.0, "y1": 0.0, "x2": 3.0, "y2": 4.0, // 距离=5 + "x1": 0.0, "y1": 0.0, "x2": 3.0, "y2": 4.0, // Distance = 5 }, { "device": "sensor1", - "temperature": 86.0, // 华氏度 + "temperature": 86.0, // The degree of the Fahrenheit degree "radius": 10.0, - "x1": 0.0, "y1": 0.0, "x2": 6.0, "y2": 8.0, // 距离=10 + "x1": 0.0, "y1": 0.0, "x2": 6.0, "y2": 8.0, // Distance = 10 }, } - // 添加结果监听器 + // Add a result listener ssql.AddSink(func(result []map[string]any) { fmt.Printf(" 📊 数学函数结果: %v\n", result) }) @@ -658,7 +658,7 @@ func testStringFunctions(ssql *streamsql.Streamsql) { return } - // 添加测试数据 + // Add test data testData := []map[string]any{ { "device": "sensor1", @@ -701,7 +701,7 @@ func testConversionFunctions(ssql *streamsql.Streamsql) { return } - // 添加测试数据 + // Add test data testData := []map[string]any{ { "device": "server1", @@ -745,7 +745,7 @@ func testAggregateFunctions(ssql *streamsql.Streamsql) { return } - // 添加测试数据 + // Add test data testData := []map[string]any{ {"device": "sensor1", "value": 2.0, "category": "A"}, {"device": "sensor1", "value": 8.0, "category": "A"}, @@ -768,16 +768,16 @@ func testAggregateFunctions(ssql *streamsql.Streamsql) { fmt.Println(" ✅ 聚合函数测试完成") } -// 展示函数管理功能 +// Showcase function management features func demonstrateFunctionManagement() { fmt.Println("\n🔧 演示函数管理功能") fmt.Println("====================") - // 列出所有函数 + // List all functions fmt.Println("\n📋 所有已注册函数:") allFunctions := functions.ListAll() - // 按类型分组显示 + // Displayed by type typeMap := make(map[functions.FunctionType][]functions.Function) for _, fn := range allFunctions { fnType := fn.GetType() @@ -791,13 +791,13 @@ func demonstrateFunctionManagement() { } } - // 演示函数查找 + // Demonstration of function search fmt.Println("\n🔍 函数查找示例:") if fn, exists := functions.Get("fahrenheit_to_celsius"); exists { fmt.Printf(" ✓ 找到函数: %s (%s)\n", fn.GetName(), fn.GetDescription()) } - // 演示按类型获取函数 + // Demonstration of obtaining functions by type fmt.Println("\n📊 数学函数列表:") mathFunctions := functions.GetByType(functions.TypeMath) for _, fn := range mathFunctions { @@ -811,7 +811,7 @@ func demonstrateFunctionManagement() { } } -// 辅助函数 +// Auxiliary function func checkError(operation string, err error) { if err != nil { fmt.Printf("❌ %s失败: %v\n", operation, err) diff --git a/examples/function-integration-demo/README.md b/examples/function-integration-demo/README.md index db890bb..896c452 100644 --- a/examples/function-integration-demo/README.md +++ b/examples/function-integration-demo/README.md @@ -1,62 +1,62 @@ -# 函数集成演示 - -## 简介 - -展示自定义函数与StreamSQL各种特性的集成使用,包括窗口聚合、表达式计算、条件过滤等。 - -## 功能演示 - -- 🪟 **窗口集成**:自定义函数在不同窗口类型中的使用 -- 🧮 **表达式集成**:函数与算术表达式的组合使用 -- 🔍 **条件集成**:在WHERE、HAVING子句中使用自定义函数 -- 📊 **聚合集成**:自定义函数与内置聚合函数的协同工作 - -## 运行方式 - -```bash -cd examples/function-integration-demo -go run main.go -``` - -## 代码亮点 - -### 1. 窗口函数集成 -```sql -SELECT - device, +# Function integration demonstration + +## Introduction + +Demonstrates the integration of custom functions with various StreamSQL features, including window aggregation, expression calculation, and conditional filtering. + +## Feature Demonstration + +- 🪟 **Window Integration**: Custom functions for use in different window types +- 🧮 **Expression Integration**: Combined use of functions and arithmetic expressions +- 🔍 **Conditional Integration**: Use custom functions in WHERE and HAVING clauses +- 📊 **Aggregation Integration**: Collaboration between custom functions and built-in aggregate functions + +## Operating Mode + +```bash +cd examples/function-integration-demo +go run main.go +``` + +## Code Highlights + +### 1. Window function integration +```sql +SELECT + device, AVG(custom_calc(temperature, pressure)) as avg_result, window_start() as start_time FROM stream -GROUP BY device, SlidingWindow('30s', '10s') -``` - -### 2. 复杂表达式集成 -```sql -SELECT - device, +GROUP BY device, SlidingWindow('30s', '10s') +``` + +### 2. Complex expression integration +```sql +SELECT + device, custom_function(value * 1.8 + 32) as processed_value, SUM(another_function(field1, field2)) as total FROM stream -GROUP BY device -``` - -### 3. 条件过滤集成 -```sql -SELECT device, AVG(temperature) -FROM stream +GROUP BY device +``` + +### 3. Conditional filtering integration +```sql +SELECT device, AVG(temperature) +FROM stream WHERE custom_validator(status) = true -HAVING custom_threshold(AVG(temperature)) > 0 -``` - -## 演示场景 - -1. **传感器数据处理** - 温度、湿度、压力的综合计算 -2. **业务指标计算** - 自定义评分和分级函数 -3. **数据清洗** - 自定义验证和转换函数 -4. **实时监控** - 阈值检查和告警函数 - -## 适用场景 - -- 🏭 **工业物联网**:复杂传感器数据处理 -- 💼 **业务分析**:自定义业务逻辑计算 -- 🔧 **系统集成**:已有函数库的整合使用 \ No newline at end of file +HAVING custom_threshold(AVG(temperature)) > 0 +``` + +## Demonstration Scene + +1. **Sensor Data Processing** - Comprehensive calculation of temperature, humidity, and pressure +2. **Business Metrics Calculation** - Custom scoring and grading functions +3. **Data Cleaning** - Custom validation and transformation functions +4. **Real-time monitoring** - threshold checks and alarm functions + +## Applicable Scenarios + +- 🏭 **Industrial IoT**: Processing of complex sensor data +- 💼 **Business Analysis**: Custom business logic calculations +- 🔧 **System Integration**: Integration and use of existing libraries diff --git a/examples/function-integration-demo/main.go b/examples/function-integration-demo/main.go index 1633a8f..823e03a 100644 --- a/examples/function-integration-demo/main.go +++ b/examples/function-integration-demo/main.go @@ -11,10 +11,10 @@ func main() { fmt.Println("🔧 StreamSQL 函数系统整合演示") fmt.Println(strings.Repeat("=", 50)) - // 1. 获取桥接器 + // 1. Get the bridge bridge := functions.GetExprBridge() - // 2. 准备测试数据 + // 2. Prepare test data data := map[string]any{ "temperature": -15.5, "humidity": 65.8, @@ -29,23 +29,23 @@ func main() { fmt.Printf("📊 测试数据: %+v\n\n", data) - // 3. 演示 StreamSQL 函数 + // 3. Demonstrate the StreamSQL function fmt.Println("🎯 StreamSQL 内置函数演示:") testStreamSQLFunctions(bridge, data) - // 4. 演示 expr-lang 函数 + // 4. Demonstrate the expr-lang function fmt.Println("\n🚀 expr-lang 内置函数演示:") testExprLangFunctions(bridge, data) - // 5. 演示混合使用 + // 5. Demonstrate mixed use fmt.Println("\n🔀 混合函数使用演示:") testMixedFunctions(bridge, data) - // 6. 演示函数冲突解决 + // 6. Demonstration of function conflict resolution fmt.Println("\n⚖️ 函数冲突解决演示:") testFunctionConflicts(bridge, data) - // 7. 显示所有可用函数 + // 7. Display all available functions fmt.Println("\n📋 所有可用函数:") showAllFunctions() } @@ -129,7 +129,7 @@ func testMixedFunctions(bridge *functions.ExprBridge, data map[string]any) { } func testFunctionConflicts(bridge *functions.ExprBridge, data map[string]any) { - // 测试冲突函数的解析 + // Test the analysis of the conflict function conflictFunctions := []string{"abs", "max", "min", "upper", "lower"} for _, funcName := range conflictFunctions { @@ -139,7 +139,7 @@ func testFunctionConflicts(bridge *functions.ExprBridge, data map[string]any) { } } - // 测试使用别名访问StreamSQL版本 + // Test accessing the StreamSQL version using aliases fmt.Println("\n 📝 使用别名访问StreamSQL函数:") env := bridge.CreateEnhancedExprEnvironment(data) if _, exists := env["streamsql_abs"]; exists { @@ -153,7 +153,7 @@ func testFunctionConflicts(bridge *functions.ExprBridge, data map[string]any) { func showAllFunctions() { info := functions.GetAllAvailableFunctions() - // StreamSQL 函数 + // StreamSQL function if streamSQLFuncs, ok := info["streamsql"].(map[string]any); ok { fmt.Printf(" 📦 StreamSQL 函数 (%d个):\n", len(streamSQLFuncs)) categories := make(map[string][]string) @@ -171,7 +171,7 @@ func showAllFunctions() { } } - // expr-lang 函数 + // expr-lang function if exprLangFuncs, ok := info["expr-lang"].(map[string]any); ok { fmt.Printf("\n 🚀 expr-lang 函数 (%d个):\n", len(exprLangFuncs)) categories := make(map[string][]string) diff --git a/examples/nested-field-examples/README.md b/examples/nested-field-examples/README.md index 37d5a55..67b00f5 100644 --- a/examples/nested-field-examples/README.md +++ b/examples/nested-field-examples/README.md @@ -1,47 +1,47 @@ -# StreamSQL 嵌套字段访问功能完整演示 - -这个演示展示了 StreamSQL 的完整嵌套字段访问功能,包括基础点号访问、复杂数组索引、Map键访问和混合操作。 - -## 功能概览 - -### 基础功能 -- **点号语法访问**:使用 `field.subfield.property` 访问嵌套结构 -- **条件过滤**:在WHERE子句中使用嵌套字段进行数据筛选 -- **聚合计算**:对嵌套字段进行GROUP BY和聚合函数操作 - -### 高级功能 -- **数组索引访问**:使用 `array[0]`、`array[-1]` 等语法访问数组元素 -- **Map键访问**:支持 `map['key']` 和 `map["key"]` 语法访问Map值 -- **混合复杂访问**:组合使用点号、数组索引、Map键进行深层嵌套访问 -- **负数索引**:支持负数索引从数组末尾开始访问元素 -- **复杂聚合**:在聚合函数中使用复杂字段访问路径 - -## 运行演示 - -```bash -cd examples/nested-field-examples -go run main.go -``` - -## 演示内容 - -### 第一部分:基础嵌套字段访问 - -#### 示例SQL -```sql -SELECT device.info.name as device_name, - device.location, +# StreamSQL Complete demonstration of nested field access functionality + +This demo demonstrates StreamSQL's complete nested field access features, including basic dot point access, complex array indexing, Map key access, and hybrid operations. + +## Feature Overview + +### Basic Features +- **Point-Marked Syntax Access**: Use `field.subfield.property` to access nested structures +- **Conditional Filtering**: Use nested fields in WHERE clauses for data filtering +- **Aggregate Computation**: Perform GROUP BY and aggregation operations on nested fields + +### Advanced Features +- **Array Index Access**: Use syntax such as `array[0]`, `array[-1]`, etc. to access array elements +- **Map key access to**: Supports `map['key']` and `map["key"]` syntax access to Map values +- **Hybrid complex access**: Combines dot numbers, array indexes, and Map keys for deep nested access +- **Negative Index**: Supports accessing elements from the end of the array using negative indexes +- **Complex Aggregation**: Use complex fields in aggregation functions to access paths + +## Run the demo + +```bash +cd examples/nested-field-examples +go run main.go +``` + +## Presentation Content + +### Part One: Basic Nested Field Access + +#### Example SQL +```sql +SELECT device.info.name as device_name, + device.location, sensor.temperature, sensor.humidity FROM stream WHERE device.location = 'room-A' - AND sensor.temperature > 20 -``` - -#### 测试数据结构 -```json -{ - "device": { + AND sensor.temperature > 20 +``` + +#### Test data structures +```json +{ + "device": { "info": { "name": "温度传感器-001", "type": "temperature" @@ -52,64 +52,64 @@ WHERE device.location = 'room-A' "temperature": 25.5, "humidity": 60.2 } -} -``` - -### 第二部分:嵌套字段聚合 - -#### 示例SQL -```sql -SELECT device.location, - AVG(sensor.temperature) as avg_temp, +} +``` + +### Part Two: Nested Field Aggregation + +#### Example SQL +```sql +SELECT device.location, + AVG(sensor.temperature) as avg_temp, MAX(sensor.humidity) as max_humidity, COUNT(*) as sensor_count FROM stream GROUP BY device.location, TumblingWindow('2s') -WITH (TIMESTAMP='timestamp', TIMEUNIT='ss') -``` - -### 第三部分:复杂嵌套字段访问 - -#### 演示1:数组索引访问 - -**SQL查询** -```sql -SELECT device, - sensors[0].temperature as first_sensor_temp, +WITH (TIMESTAMP='timestamp', TIMEUNIT='ss') +``` + +### Part Three: Accessing Complex Nested Fields + +#### Demo 1: Array Index Access + +**SQL Query** +```sql +SELECT device, + sensors[0].temperature as first_sensor_temp, sensors[1].humidity as second_sensor_humidity, data[2] as third_data_item -FROM stream -``` - -**数据结构** -```json -{ - "device": "工业传感器-001", +FROM stream +``` + +**Data Structure** +```json +{ + "device": "工业传感器-001", "sensors": [ {"temperature": 25.5, "humidity": 60.2}, {"temperature": 26.8, "humidity": 58.7}, {"temperature": 24.1, "humidity": 62.1} ], "data": ["status_ok", "battery_95%", "signal_strong", "location_A1"] -} -``` - -#### 演示2:Map键访问 - -**SQL查询** -```sql -SELECT device_id, - config['host'] as server_host, +} +``` + +#### Demo 2: Map Key Access + +**SQL Query** +```sql +SELECT device_id, + config['host'] as server_host, config["port"] as server_port, settings['enable_ssl'] as ssl_enabled, metadata["version"] as app_version -FROM stream -``` - -**数据结构** -```json -{ - "device_id": "gateway-001", +FROM stream +``` + +**Data Structure** +```json +{ + "device_id": "gateway-001", "config": { "host": "192.168.1.100", "port": 8080, @@ -119,25 +119,25 @@ FROM stream "enable_ssl": true, "timeout": 30 } -} -``` - -#### 演示3:混合复杂访问 - -**SQL查询** -```sql -SELECT building, - floors[0].rooms[2]['name'] as first_floor_room3_name, +} +``` + +#### Demo 3: Hybrid Complex Access + +**SQL Query** +```sql +SELECT building, + floors[0].rooms[2]['name'] as first_floor_room3_name, floors[1].sensors[0].readings['temperature'] as second_floor_first_sensor_temp, metadata.building_info['architect'] as building_architect, alerts[-1].message as latest_alert -FROM stream -``` - -**数据结构** -```json -{ - "building": "智能大厦A座", +FROM stream +``` + +**Data Structure** +```json +{ + "building": "智能大厦A座", "floors": [ { "floor_number": 1, @@ -160,131 +160,131 @@ FROM stream ] } ] -} -``` - -#### 演示4:负数索引访问 - -**SQL查询** -```sql -SELECT device_name, - readings[-1] as latest_reading, +} +``` + +#### Demo 4: Negative Index Access + +**SQL Query** +```sql +SELECT device_name, + readings[-1] as latest_reading, history[-2] as second_last_event, tags[-1] as last_tag -FROM stream -``` - -**数据结构** -```json -{ - "device_name": "温度监测器-Alpha", +FROM stream +``` + +**Data Structure** +```json +{ + "device_name": "温度监测器-Alpha", "readings": [18.5, 19.2, 20.1, 21.3, 22.8, 23.5], "history": ["boot", "calibration", "running", "alert", "resolved"], "tags": ["indoor", "critical", "monitored"] -} -``` - -#### 演示5:数组索引聚合计算 - -**SQL查询** -```sql -SELECT location, - AVG(sensors[0].temperature) as avg_first_sensor_temp, +} +``` + +#### Demo 5: Array Index Aggregation Calculation + +**SQL Query** +```sql +SELECT location, + AVG(sensors[0].temperature) as avg_first_sensor_temp, MAX(sensors[1].humidity) as max_second_sensor_humidity, COUNT(*) as device_count FROM stream GROUP BY location, TumblingWindow('2s') -WITH (TIMESTAMP='timestamp', TIMEUNIT='ss') -``` - -## 支持的访问语法总结 - -| 语法类型 | 示例 | 说明 | -|---------|------|------| -| 点号访问 | `device.info.name` | 基础嵌套字段访问 | -| 数组正索引 | `items[0]`, `data[2]` | 从0开始的数组索引 | -| 数组负索引 | `items[-1]`, `data[-2]` | 从末尾开始的数组索引 | -| Map单引号键 | `config['host']` | 使用单引号的Map键访问 | -| Map双引号键 | `settings["timeout"]` | 使用双引号的Map键访问 | -| 混合访问 | `users[0].profile['name']` | 组合多种访问方式 | -| 多维数组 | `matrix[1][2]` | 二维或多维数组访问 | - -## 预期输出示例 - -### 基础嵌套字段访问结果 -``` -📊 第一部分:基础嵌套字段访问 - 📋 基础嵌套字段访问结果: +WITH (TIMESTAMP='timestamp', TIMEUNIT='ss') +``` + +## Summary of supported access syntax + +| Grammatical type | Example | Note | +|---------|------|------| +| Click access | `device.info.name` | Basic nested field access | +| Array positive index | `items[0]`, `data[2]` | Array index starting from 0 | +| Array negative index | `items[-1]`, `data[-2]` | Array index |, starting from the end +| Map Single quote key | `config['host']` | Use the single-quoted Map key to access | +| Map Double quotation key | `settings["timeout"]` | Access |using the Map key in double quotes +| Hybrid Access | `users[0].profile['name']` | Combine multiple access methods | +| Multidimensional array | `matrix[1][2]` | Two-dimensional or multidimensional array access | + +## Example of expected output + +### Basic Nested Field Access Results +``` +📊 第一部分:基础嵌套字段访问 + 📋 基础嵌套字段访问结果: 记录 1: 设备名称: 温度传感器-001 设备位置: room-A 温度: 25.5°C - 湿度: 60.2% -``` - -### 数组索引访问结果 -``` -📊 演示1: 数组索引访问 - 📋 数组索引访问结果: + 湿度: 60.2% +``` + +### Array index access result +``` +📊 演示1: 数组索引访问 + 📋 数组索引访问结果: 记录 1: 设备: 工业传感器-001 第一个传感器温度: 25.5°C 第二个传感器湿度: 58.7% - 第三个数据项: signal_strong -``` - -### Map键访问结果 -``` -🗝️ 演示2: Map键访问 - 🗝️ Map键访问结果: + 第三个数据项: signal_strong +``` + +### Map key to access results +``` +🗝️ 演示2: Map键访问 + 🗝️ Map键访问结果: 记录 1: 设备ID: gateway-001 服务器主机: 192.168.1.100 服务器端口: 8080 SSL启用: true - 应用版本: v2.1.3 -``` - -### 聚合计算结果 -``` -📈 演示5: 数组索引聚合计算 - 📈 数组索引聚合计算结果: + 应用版本: v2.1.3 +``` + +### Aggregated calculation results +``` +📈 演示5: 数组索引聚合计算 + 📈 数组索引聚合计算结果: 聚合结果 1: 位置: 车间A 第一个传感器平均温度: 24.83°C 第二个传感器最大湿度: 68.2% - 设备数量: 4 -``` - -## 技术特性 - -### 性能优化 -- 字段路径解析结果缓存 -- 高效的嵌套访问算法 -- 最小化内存分配 - -### 错误处理 -- 安全的数组边界检查 -- 优雅的空值处理 -- 类型安全的访问操作 - -### 兼容性 -- 完全向后兼容简单字段访问 -- 支持所有现有SQL功能 -- 无缝集成聚合和窗口函数 - -## 使用建议 - -1. **字段别名**:为复杂路径使用AS别名提高可读性 -2. **数据验证**:在生产环境中验证数据结构的一致性 -3. **性能考虑**:避免过深的嵌套层级(建议不超过5层) -4. **错误预期**:预期并处理字段不存在的情况 -5. **测试覆盖**:全面测试各种数据结构和边界情况 - -## 注意事项 - -- 数组索引从0开始,负数索引从-1开始 -- Map键访问区分大小写 -- 访问不存在的字段返回null而不抛出异常 -- 所有操作都是线程安全的 -- 支持实时流处理和批处理模式 \ No newline at end of file + 设备数量: 4 +``` + +## Technical Features + +### Performance Optimization +- Caching field path parsing results +- Efficient nested access algorithms +- Minimizes memory allocation + +### Error Handling +- Secure array boundary checks +- Elegant null value processing +- Type-safe access operations + +### Compatibility +- Fully backward compatible with simple field access +- Supports all existing SQL features +- Seamless integration of aggregation and window functions + +## Usage Recommendations + +1. **Field Aliases**: Use AS Aliases for complex paths to improve readability +2. **Data Validation**: Verify the consistency of data structures in production environments +3. **Performance Considerations**: Avoid overly deep nested layers (recommended not to exceed 5 layers) +4. **Error Expectation**: Anticipate and handle cases where fields do not exist +5. **Test coverage**: Comprehensive testing of various data structures and boundary situations + +## Notes + +- The array index starts from 0, and the negative index starts at -1 +- Map Key access is case-sensitive +- Returns null to non-existent fields without throwing exceptions +- All operations are thread-safe +- Supports real-time stream processing and batch processing modes diff --git a/examples/nested-field-examples/main.go b/examples/nested-field-examples/main.go index 1b103ee..ad74b0e 100644 --- a/examples/nested-field-examples/main.go +++ b/examples/nested-field-examples/main.go @@ -14,47 +14,47 @@ func main() { fmt.Println("🔧 StreamSQL 嵌套字段访问功能完整演示") fmt.Println("=========================================") - // 创建 StreamSQL 实例 + // Create a StreamSQL instance ssql := streamsql.New() defer ssql.Stop() - // 基础功能演示 + // Basic function demonstration fmt.Println("\n📊 第一部分:基础嵌套字段访问") demonstrateBasicNestedAccess(ssql) - // 基础聚合演示 + // Basic aggregation demonstration fmt.Println("\n📈 第二部分:嵌套字段聚合") demonstrateNestedAggregation(ssql) - // 复杂功能演示 + // Complex feature demonstration fmt.Println("\n🔧 第三部分:复杂嵌套字段访问") - // 演示1: 数组索引访问 + // Demo 1: Array index access fmt.Println("\n📊 演示1: 数组索引访问") demonstrateArrayAccess(ssql) - // 演示2: Map键访问 + // Demo 2: Map key access fmt.Println("\n🗝️ 演示2: Map键访问") demonstrateMapKeyAccess(ssql) - // 演示3: 混合复杂访问 + // Demo 3: Hybrid Complex Access fmt.Println("\n🔄 演示3: 混合复杂访问") demonstrateComplexMixedAccess(ssql) - // 演示4: 负数索引访问 + // Demo 4: Negative Index Access fmt.Println("\n⬅️ 演示4: 负数索引访问") demonstrateNegativeIndexAccess(ssql) - // 演示5: 数组索引聚合计算 + // Demo 5: Array Index Aggregation Computation fmt.Println("\n📈 演示5: 数组索引聚合计算") demonstrateArrayIndexAggregation(ssql) fmt.Println("\n✅ 完整演示完成!") } -// 演示基础嵌套字段访问 +// Demonstration of basic nested field access func demonstrateBasicNestedAccess(ssql *streamsql.Streamsql) { - // SQL查询使用基础嵌套字段 + // SQL queries use basic nested fields rsql := `SELECT device.info.name as device_name, device.location, sensor.temperature, @@ -69,7 +69,7 @@ func demonstrateBasicNestedAccess(ssql *streamsql.Streamsql) { return } - // 准备测试数据 + // Prepare test data testData := []map[string]any{ { "device": map[string]any{ @@ -91,7 +91,7 @@ func demonstrateBasicNestedAccess(ssql *streamsql.Streamsql) { "name": "温度传感器-002", "type": "temperature", }, - "location": "room-B", // 不匹配条件 + "location": "room-B", // Mismatched conditions }, "sensor": map[string]any{ "temperature": 30.0, @@ -108,7 +108,7 @@ func demonstrateBasicNestedAccess(ssql *streamsql.Streamsql) { "location": "room-A", }, "sensor": map[string]any{ - "temperature": 15.0, // 不匹配条件 + "temperature": 15.0, // Mismatched conditions "humidity": 65.3, }, "timestamp": time.Now().Unix(), @@ -118,7 +118,7 @@ func demonstrateBasicNestedAccess(ssql *streamsql.Streamsql) { var wg sync.WaitGroup wg.Add(1) - // 设置结果回调 + // Set result callback ssql.AddSink(func(result []map[string]any) { defer wg.Done() @@ -134,18 +134,18 @@ func demonstrateBasicNestedAccess(ssql *streamsql.Streamsql) { } }) - // 添加测试数据 + // Add test data for _, data := range testData { ssql.Emit(data) } - // 等待结果 + // Wait for the results wg.Wait() } -// 演示嵌套字段聚合 +// Demonstration of nested field aggregation func demonstrateNestedAggregation(ssql *streamsql.Streamsql) { - // SQL查询:嵌套字段聚合 + // SQL Query: Nested field aggregation rsql := `SELECT device.location, AVG(sensor.temperature) as avg_temp, MAX(sensor.humidity) as max_humidity, @@ -164,7 +164,7 @@ func demonstrateNestedAggregation(ssql *streamsql.Streamsql) { var wg sync.WaitGroup wg.Add(1) - // 设置结果回调 + // Set result callback ssql.AddSink(func(result []map[string]any) { defer wg.Done() @@ -181,7 +181,7 @@ func demonstrateNestedAggregation(ssql *streamsql.Streamsql) { } }) - // 生成模拟数据 + // Generate simulated data locations := []string{"智能温室-A区", "智能温室-B区", "智能温室-C区"} go func() { @@ -204,11 +204,11 @@ func demonstrateNestedAggregation(ssql *streamsql.Streamsql) { } ssql.Emit(data) - time.Sleep(300 * time.Millisecond) // 每300ms发送一条数据 + time.Sleep(300 * time.Millisecond) // Transmits a data piece every 300ms } }() - // 等待聚合结果 + // Waiting for the aggregated results ctx, cancel := context.WithTimeout(context.Background(), 6*time.Second) defer cancel() @@ -227,9 +227,9 @@ func demonstrateNestedAggregation(ssql *streamsql.Streamsql) { } } -// 演示数组索引访问 +// Demonstration of array index access func demonstrateArrayAccess(ssql *streamsql.Streamsql) { - // SQL查询:提取数组中的特定元素 + // SQL query: extracting specific elements from an array rsql := `SELECT device, sensors[0].temperature as first_sensor_temp, sensors[1].humidity as second_sensor_humidity, @@ -242,7 +242,7 @@ func demonstrateArrayAccess(ssql *streamsql.Streamsql) { return } - // 准备测试数据 + // Prepare test data testData := []map[string]any{ { "device": "工业传感器-001", @@ -268,7 +268,7 @@ func demonstrateArrayAccess(ssql *streamsql.Streamsql) { var wg sync.WaitGroup wg.Add(1) - // 设置结果回调 + // Set result callback ssql.AddSink(func(result []map[string]any) { defer wg.Done() @@ -284,18 +284,18 @@ func demonstrateArrayAccess(ssql *streamsql.Streamsql) { } }) - // 添加测试数据 + // Add test data for _, data := range testData { ssql.Emit(data) } - // 等待结果 + // Wait for the results wg.Wait() } -// 演示Map键访问 +// Demonstration of Map key access func demonstrateMapKeyAccess(ssql *streamsql.Streamsql) { - // SQL查询:使用字符串键访问Map数据 + // SQL query: Accessing Map data using string keys rsql := `SELECT device_id, config['host'] as server_host, config["port"] as server_port, @@ -309,7 +309,7 @@ func demonstrateMapKeyAccess(ssql *streamsql.Streamsql) { return } - // 准备测试数据 + // Prepare test data testData := []map[string]any{ { "device_id": "gateway-001", @@ -352,7 +352,7 @@ func demonstrateMapKeyAccess(ssql *streamsql.Streamsql) { var wg sync.WaitGroup wg.Add(1) - // 设置结果回调 + // Set result callback ssql.AddSink(func(result []map[string]any) { defer wg.Done() @@ -369,18 +369,18 @@ func demonstrateMapKeyAccess(ssql *streamsql.Streamsql) { } }) - // 添加测试数据 + // Add test data for _, data := range testData { ssql.Emit(data) } - // 等待结果 + // Wait for the results wg.Wait() } -// 演示混合复杂访问 +// Demonstration of hybrid complex access func demonstrateComplexMixedAccess(ssql *streamsql.Streamsql) { - // SQL查询:混合使用数组索引、Map键和嵌套字段访问 + // SQL queries: Combining array indexes, Map keys, and nested field access rsql := `SELECT building, floors[0].rooms[2]['name'] as first_floor_room3_name, floors[1].sensors[0].readings['temperature'] as second_floor_first_sensor_temp, @@ -394,11 +394,11 @@ func demonstrateComplexMixedAccess(ssql *streamsql.Streamsql) { return } - // 准备复杂嵌套数据 + // Prepare complex nested data testData := map[string]any{ "building": "智能大厦A座", "floors": []any{ - // 第一层 + // The first level map[string]any{ "floor_number": 1, "rooms": []any{ @@ -408,7 +408,7 @@ func demonstrateComplexMixedAccess(ssql *streamsql.Streamsql) { map[string]any{"name": "休息区", "type": "lounge"}, }, }, - // 第二层 + // The second floor map[string]any{ "floor_number": 2, "sensors": []any{ @@ -449,7 +449,7 @@ func demonstrateComplexMixedAccess(ssql *streamsql.Streamsql) { var wg sync.WaitGroup wg.Add(1) - // 设置结果回调 + // Set result callback ssql.AddSink(func(result []map[string]any) { defer wg.Done() @@ -466,16 +466,16 @@ func demonstrateComplexMixedAccess(ssql *streamsql.Streamsql) { } }) - // 添加数据 + // Add data ssql.Emit(testData) - // 等待结果 + // Wait for the results wg.Wait() } -// 演示负数索引访问 +// Demonstrating negative index access func demonstrateNegativeIndexAccess(ssql *streamsql.Streamsql) { - // SQL查询:使用负数索引访问数组末尾元素 + // SQL query: Uses negative indexes to access the last element of an array rsql := `SELECT device_name, readings[-1] as latest_reading, history[-2] as second_last_event, @@ -488,7 +488,7 @@ func demonstrateNegativeIndexAccess(ssql *streamsql.Streamsql) { return } - // 准备测试数据 + // Prepare test data testData := []map[string]any{ { "device_name": "温度监测器-Alpha", @@ -507,7 +507,7 @@ func demonstrateNegativeIndexAccess(ssql *streamsql.Streamsql) { var wg sync.WaitGroup wg.Add(1) - // 设置结果回调 + // Set result callback ssql.AddSink(func(result []map[string]any) { defer wg.Done() @@ -523,18 +523,18 @@ func demonstrateNegativeIndexAccess(ssql *streamsql.Streamsql) { } }) - // 添加测试数据 + // Add test data for _, data := range testData { ssql.Emit(data) } - // 等待结果 + // Wait for the results wg.Wait() } -// 演示数组索引聚合计算 +// Demonstration of array index aggregation calculation func demonstrateArrayIndexAggregation(ssql *streamsql.Streamsql) { - // SQL查询:对数组中特定位置的数据进行聚合计算 + // SQL Query: Aggregates data at specific locations in an array for calculation rsql := `SELECT location, AVG(sensors[0].temperature) as avg_first_sensor_temp, MAX(sensors[1].humidity) as max_second_sensor_humidity, @@ -553,7 +553,7 @@ func demonstrateArrayIndexAggregation(ssql *streamsql.Streamsql) { var wg sync.WaitGroup wg.Add(1) - // 设置结果回调 + // Set result callback ssql.AddSink(func(result []map[string]any) { defer wg.Done() @@ -570,7 +570,7 @@ func demonstrateArrayIndexAggregation(ssql *streamsql.Streamsql) { } }) - // 生成模拟数据 + // Generate simulated data locations := []string{"车间A", "车间B", "车间C"} go func() { @@ -594,11 +594,11 @@ func demonstrateArrayIndexAggregation(ssql *streamsql.Streamsql) { } ssql.Emit(data) - time.Sleep(200 * time.Millisecond) // 每200ms发送一条数据 + time.Sleep(200 * time.Millisecond) // Send a data piece every 200ms } }() - // 等待聚合结果 + // Waiting for the aggregated results ctx, cancel := context.WithTimeout(context.Background(), 8*time.Second) defer cancel() diff --git a/examples/non-aggregation/README.md b/examples/non-aggregation/README.md index c9e4392..8362a2f 100644 --- a/examples/non-aggregation/README.md +++ b/examples/non-aggregation/README.md @@ -1,122 +1,122 @@ -# StreamSQL 非聚合场景使用示例 +# StreamSQL Example of non-aggregated scenario usage -本示例展示了StreamSQL在非聚合场景中的各种应用,包括实时数据转换、过滤、清洗等功能。 +This example demonstrates various applications of StreamSQL in non-aggregated scenarios, including real-time data conversion, filtering, cleaning, and more. -## 运行示例 +## Running Example ```bash cd examples/non-aggregation go run main.go ``` -## 场景说明 - -### 1. 实时数据清洗和标准化 - -**场景描述**: 对输入的脏数据进行清洗和标准化处理,包括: -- 空值处理(COALESCE) -- 字符串规范化(UPPER, TRIM) -- 数值精度处理(ROUND) -- 状态码转换(CASE WHEN) -- 无效数据过滤(WHERE条件) - -**适用场景**: -- IoT设备数据清洗 -- 日志标准化处理 -- 数据质量保证 - -### 2. 数据富化和计算字段 - -**场景描述**: 基于原始数据计算和添加新字段,包括: -- 单位转换(摄氏度转华氏度) -- 分类标签生成(温度分级) -- 字符串拼接(全标识符) -- 时间戳添加 -- 比率计算 - -**适用场景**: -- 数据预处理 -- 业务规则应用 -- 指标计算 - -### 3. 实时告警和事件过滤 - -**场景描述**: 实时检测异常数据并生成告警事件,包括: -- 阈值检测 -- 告警级别分类 -- 告警消息生成 -- 时间戳记录 - -**适用场景**: -- 监控系统 -- 异常检测 -- 实时告警 - -### 4. 数据格式转换 - -**场景描述**: 将数据转换为不同的格式,包括: -- JSON格式输出 -- CSV格式输出 -- 自定义格式转换 - -**适用场景**: -- 数据接口适配 -- 多系统集成 -- 数据导出 - -### 5. 基于条件的数据路由 - -**场景描述**: 根据数据内容决定数据的路由目标,包括: -- 条件路由规则 -- 优先级分类 -- 主题分发 - -**适用场景**: -- 消息队列路由 -- 数据分发 -- 负载均衡 - -### 6. 嵌套字段处理 - -**场景描述**: 处理复杂的嵌套JSON数据,包括: -- 深层字段提取 -- 嵌套字段组合 -- 条件判断 - -**适用场景**: -- JSON数据处理 -- 复杂数据结构解析 -- API数据转换 - -## 核心特性 - -### 实时处理 -- 每条数据立即处理,无需等待窗口 -- 超低延迟,适合实时场景 -- 支持高吞吐量数据流 - -### 丰富的函数支持 -- 字符串处理:UPPER, LOWER, TRIM, CONCAT, SUBSTRING等 -- 数学计算:ROUND, CAST, 算术运算等 -- 条件判断:CASE WHEN, COALESCE, IF等 -- 时间函数:NOW, DATE_FORMAT等 -- 类型转换:CAST, TO_JSON等 - -### 灵活的字段操作 -- 字段选择和别名 -- 嵌套字段访问(点号语法) -- 计算字段生成 -- 表达式计算 - -### 强大的过滤能力 -- WHERE条件过滤 -- 复杂表达式支持 -- 多条件组合(AND, OR) -- 模式匹配(LIKE 语法) - -## 性能特点 - -- **低延迟**: 每条数据立即处理输出 -- **高吞吐**: 支持高频数据流 -- **内存友好**: 无需缓存数据,即时处理 -- **CPU高效**: 简单的数据转换操作 +## Scenario description + +### 1. Real-time data cleaning and standardization + +**Scenario Description**: Cleaning and standardizing dirty input data, including: +- Null Value Handling (COALESCE) +- String normalization (UPPER, TRIM) +- Numerical Precision Processing (ROUND) +- Status code conversion (CASE WHEN) +- Invalid data filtering (WHERE condition) + +**Applicable Scenarios**: +- IoT Equipment data cleaning +- Log standardization +- Data quality assurance + +### 2. Data enrichment and computation fields + +**Scenario Description**: Calculate and add new fields based on raw data, including: +- Unit conversion (Celsius to Fahrenheit) +- Classification label generation (temperature grading) +- String concatenation (full identifier) +- Add timestamps +- Ratio calculation + +**Applicable Scenarios**: +- Data preprocessing +- Application of business rules +- Indicator calculation + +### 3. Real-time alerts and event filtering + +**Scenario Description**: Real-time detection of abnormal data and generation of alarm events, including: +- Threshold detection +- Alarm level classification +- Alert message generation +- Timestamp records + +**Applicable Scenarios**: +- Monitoring system +- Anomaly detection +- Real-time alerts + +### 4. Data format conversion + +**Scenario Description**: Convert data into different formats, including: +- JSON Format output +- CSV Format output +- Custom format conversion + +**Applicable Scenarios**: +- Data interface adaptation +- Multi-system integration +- Data export + +### 5. Condition-based data routing + +**Scenario Description**: Determine the routing target based on the data content, including: +- Conditional routing rules +- Priority classification +- Themed distribution + +**Applicable Scenarios**: +- Message queue routing +- Data distribution +- Load balancing + +### 6. Nested field processing + +**Scenario Description**: Handling complex nested JSON data, including: +- Deep field extraction +- Nested field combinations +- Conditional judgment + +**Applicable Scenarios**: +- JSON Data processing +- Parsing complex data structures +- API Data transformation + +## Core Features + +### Real-time processing +- Every data entry is processed instantly, with no waiting windows +- Ultra-low latency, suitable for real-time scenarios +- Supports high-throughput data streams + +### Rich function support +- String handling: UPPER, LOWER, TRIM, CONCAT, SUBSTRING, etc +- Mathematical calculation: ROUND, CAST, arithmetic operations, etc +- Conditional judgment: CASE WHEN, COALESCE, IF, etc +- Time function: NOW, DATE_FORMAT, etc +- Type conversion: CAST, TO_JSON, etc + +### Flexible field operations +- Field selection and aliases +- Nested Field Access (Dotted Syntax) +- Calculate field generation +- Expression calculation + +### Powerful filtration capabilities +- WHERE Conditional filtering +- Support for complex expressions +- Multi-conditional combination (AND, OR) +- Pattern matching (LIKE syntax) + +## Performance Features + +- **Low latency**: Each data item is processed as output immediately +- **High Throughput**: Supports high-frequency data streams +- **Memory-friendly**: No need for caching data, instant processing +- **CPU Efficient**: Simple data conversion operations diff --git a/examples/non-aggregation/main.go b/examples/non-aggregation/main.go index 32b907c..7b3e1ec 100644 --- a/examples/non-aggregation/main.go +++ b/examples/non-aggregation/main.go @@ -8,44 +8,44 @@ import ( "github.com/rulego/streamsql" ) -// 非聚合场景使用示例 -// 展示StreamSQL在实时数据转换、过滤、清洗等场景中的应用 +// Example of use in non-aggregated scenarios +// Showcasing the application of StreamSQL in real-time data transformation, filtering, cleaning, and other scenarios func main() { fmt.Println("=== StreamSQL 非聚合场景演示 ===") - // 场景1: 实时数据清洗和标准化 + // Scenario 1: Real-time data cleaning and standardization fmt.Println("\n1. 实时数据清洗和标准化") demonstrateDataCleaning() - // 场景2: 数据富化和计算字段 + // Scenario 2: Data Richening and Computation Fields fmt.Println("\n2. 数据富化和计算字段") demonstrateDataEnrichment() - // 场景3: 实时告警和事件过滤 + // Scenario 3: Real-time alerts and event filtering fmt.Println("\n3. 实时告警和事件过滤") demonstrateRealTimeAlerting() - // 场景4: 数据格式转换 + // Scenario 4: Data format conversion fmt.Println("\n4. 数据格式转换") demonstrateDataFormatConversion() - // 场景5: 基于条件的数据路由 + // Scenario 5: Condition-based data routing fmt.Println("\n5. 基于条件的数据路由") demonstrateDataRouting() - // 场景6: 嵌套字段处理 + // Scenario 6: Nested Field Handling fmt.Println("\n6. 嵌套字段处理") demonstrateNestedFieldProcessing() fmt.Println("\n=== 演示完成 ===") } -// 场景1: 实时数据清洗和标准化 +// Scenario 1: Real-time data cleaning and standardization func demonstrateDataCleaning() { ssql := streamsql.New() defer ssql.Stop() - // 清洗和标准化SQL + // Cleaning and standardizing SQL rsql := `SELECT deviceId, UPPER(TRIM(deviceType)) as device_type, ROUND(temperature, 2) as temperature, @@ -61,16 +61,16 @@ func demonstrateDataCleaning() { panic(err) } - // 结果处理 + // Result processing ssql.AddSink(func(result []map[string]any) { fmt.Printf(" 清洗后数据: %+v\n", result) }) - // 模拟脏数据输入 + // Simulating dirty data input dirtyData := []map[string]any{ {"deviceId": "sensor001", "deviceType": " temperature ", "temperature": 25.456789, "location": "room1", "status": 1}, {"deviceId": "sensor002", "deviceType": "humidity", "temperature": 60.123, "location": nil, "status": 0}, - {"deviceId": "", "deviceType": "pressure", "temperature": nil, "location": "room2", "status": 2}, // 应被过滤 + {"deviceId": "", "deviceType": "pressure", "temperature": nil, "location": "room2", "status": 2}, // It should be filtered {"deviceId": "sensor003", "deviceType": "TEMPERATURE", "temperature": 22.7, "location": "room3", "status": 1}, } @@ -82,12 +82,12 @@ func demonstrateDataCleaning() { time.Sleep(200 * time.Millisecond) } -// 场景2: 数据富化和计算字段 +// Scenario 2: Data Richening and Computation Fields func demonstrateDataEnrichment() { ssql := streamsql.New() defer ssql.Stop() - // 数据富化SQL + // Data Rich SQL rsql := `SELECT *, temperature * 1.8 + 32 as temp_fahrenheit, CASE WHEN temperature > 30 THEN 'hot' @@ -107,7 +107,7 @@ func demonstrateDataEnrichment() { fmt.Printf(" 富化后数据: %+v\n", result) }) - // 原始数据 + // Raw data rawData := []map[string]any{ {"deviceId": "sensor001", "temperature": 32.5, "humidity": 65, "location": "greenhouse"}, {"deviceId": "sensor002", "temperature": 12.0, "humidity": 45, "location": "warehouse"}, @@ -122,12 +122,12 @@ func demonstrateDataEnrichment() { time.Sleep(200 * time.Millisecond) } -// 场景3: 实时告警和事件过滤 +// Scenario 3: Real-time alerts and event filtering func demonstrateRealTimeAlerting() { ssql := streamsql.New() defer ssql.Stop() - // 告警过滤SQL + // Alert filtering SQL rsql := `SELECT deviceId, temperature, humidity, @@ -151,14 +151,14 @@ func demonstrateRealTimeAlerting() { fmt.Printf(" 🚨 告警事件: %+v\n", result) }) - // 模拟传感器数据(包含异常值) + // Simulated sensor data (including outliers) sensorData := []map[string]any{ - {"deviceId": "sensor001", "temperature": 25.0, "humidity": 60, "location": "room1"}, // 正常 - {"deviceId": "sensor002", "temperature": 45.0, "humidity": 50, "location": "room2"}, // 高温告警 - {"deviceId": "sensor003", "temperature": 20.0, "humidity": 95, "location": "room3"}, // 高湿度告警 - {"deviceId": "sensor004", "temperature": 2.0, "humidity": 30, "location": "room4"}, // 低温告警 - {"deviceId": "sensor005", "temperature": 22.0, "humidity": 15, "location": "room5"}, // 低湿度告警 - {"deviceId": "sensor006", "temperature": 24.0, "humidity": 55, "location": "room6"}, // 正常 + {"deviceId": "sensor001", "temperature": 25.0, "humidity": 60, "location": "room1"}, // Normal + {"deviceId": "sensor002", "temperature": 45.0, "humidity": 50, "location": "room2"}, // High temperature alert + {"deviceId": "sensor003", "temperature": 20.0, "humidity": 95, "location": "room3"}, // High humidity alarm + {"deviceId": "sensor004", "temperature": 2.0, "humidity": 30, "location": "room4"}, // Low temperature alert + {"deviceId": "sensor005", "temperature": 22.0, "humidity": 15, "location": "room5"}, // Low humidity alarm + {"deviceId": "sensor006", "temperature": 24.0, "humidity": 55, "location": "room6"}, // Normal } for _, data := range sensorData { @@ -169,12 +169,12 @@ func demonstrateRealTimeAlerting() { time.Sleep(200 * time.Millisecond) } -// 场景4: 数据格式转换 +// Scenario 4: Data format conversion func demonstrateDataFormatConversion() { ssql := streamsql.New() defer ssql.Stop() - // 格式转换SQL + // Format conversion SQL rsql := `SELECT deviceId, CONCAT('{"device_id":"', deviceId, '","metrics":{"temp":', CAST(temperature AS STRING), ',"hum":', @@ -195,7 +195,7 @@ func demonstrateDataFormatConversion() { fmt.Printf(" 格式转换结果: %+v\n", result) }) - // 输入数据 + // Enter data inputData := []map[string]any{ {"deviceId": "sensor001", "temperature": 25.5, "humidity": 60, "location": "warehouse-A"}, {"deviceId": "sensor002", "temperature": 22.0, "humidity": 55, "location": "warehouse-B"}, @@ -209,12 +209,12 @@ func demonstrateDataFormatConversion() { time.Sleep(200 * time.Millisecond) } -// 场景5: 基于条件的数据路由 +// Scenario 5: Condition-based data routing func demonstrateDataRouting() { ssql := streamsql.New() defer ssql.Stop() - // 数据路由SQL + // Data routing SQL rsql := `SELECT *, CASE WHEN deviceType = 'temperature' AND temperature > 30 THEN 'high_temp_topic' WHEN deviceType = 'humidity' AND humidity > 80 THEN 'high_humidity_topic' @@ -234,7 +234,7 @@ func demonstrateDataRouting() { fmt.Printf(" 路由结果: %+v\n", result) }) - // 不同类型的设备数据 + // Data from different types of devices deviceData := []map[string]any{ {"deviceId": "temp001", "deviceType": "temperature", "temperature": 35.0, "humidity": 60}, {"deviceId": "hum001", "deviceType": "humidity", "temperature": 25.0, "humidity": 85}, @@ -250,12 +250,12 @@ func demonstrateDataRouting() { time.Sleep(200 * time.Millisecond) } -// 场景6: 嵌套字段处理 +// Scenario 6: Nested Field Handling func demonstrateNestedFieldProcessing() { ssql := streamsql.New() defer ssql.Stop() - // 嵌套字段处理SQL + // Nested fields handle SQL rsql := `SELECT device.info.id as device_id, device.info.name as device_name, device.location.building as building, @@ -277,7 +277,7 @@ func demonstrateNestedFieldProcessing() { fmt.Printf(" 嵌套字段处理结果: %+v\n", result) }) - // 嵌套结构数据 + // Nested structural data nestedData := []map[string]any{ { "device": map[string]any{ @@ -331,11 +331,11 @@ func demonstrateNestedFieldProcessing() { time.Sleep(200 * time.Millisecond) } -// 生成随机测试数据的辅助函数 +// Auxiliary functions that generate random test data func generateRandomSensorData(deviceId string) map[string]any { return map[string]any{ "deviceId": deviceId, - "temperature": 15.0 + rand.Float64()*25.0, // 15-40度 + "temperature": 15.0 + rand.Float64()*25.0, // 15-40 degrees "humidity": 30.0 + rand.Float64()*40.0, // 30-70% "location": fmt.Sprintf("room%d", rand.Intn(10)+1), "timestamp": time.Now().Unix(), diff --git a/examples/null-comparison-examples/main.go b/examples/null-comparison-examples/main.go index fecfacc..9f9ce11 100644 --- a/examples/null-comparison-examples/main.go +++ b/examples/null-comparison-examples/main.go @@ -11,11 +11,11 @@ func main() { fmt.Println("=== StreamSQL Null 比较语法演示 ===") fmt.Println() - demo1() // fieldName = nil 语法 - demo2() // fieldName != nil 语法 - demo3() // fieldName = null 和 != null 语法 - demo4() // 混合语法演示 - demo5() // 嵌套字段 null 比较 + demo1() // fieldName = nil syntax + demo2() // fieldName!= nil syntax + demo3() // fieldName = null and!= null syntax + demo4() // Mixed grammar demonstration + demo5() // Nested field null comparison } func demo1() { @@ -25,7 +25,7 @@ func demo1() { ssql := streamsql.New() defer ssql.Stop() - // 使用 = nil 语法查找空值 + // Use the = nil syntax to find null values rsql := `SELECT deviceId, value, status FROM stream WHERE value = nil` @@ -43,9 +43,9 @@ func demo1() { testData := []map[string]any{ {"deviceId": "sensor1", "value": 25.5, "status": "active"}, - {"deviceId": "sensor2", "value": nil, "status": "active"}, // 符合条件 + {"deviceId": "sensor2", "value": nil, "status": "active"}, // Meet the requirements {"deviceId": "sensor3", "value": 30.0, "status": "inactive"}, - {"deviceId": "sensor4", "value": nil, "status": "error"}, // 符合条件 + {"deviceId": "sensor4", "value": nil, "status": "error"}, // Meet the requirements } for _, data := range testData { @@ -63,7 +63,7 @@ func demo2() { ssql := streamsql.New() defer ssql.Stop() - // 使用 != nil 语法查找非空值 + // Use the!= nil syntax to find non-null values rsql := `SELECT deviceId, value, status FROM stream WHERE value != nil AND value > 20` @@ -80,10 +80,10 @@ func demo2() { }) testData := []map[string]any{ - {"deviceId": "sensor1", "value": 25.5, "status": "active"}, // 符合条件 - {"deviceId": "sensor2", "value": nil, "status": "active"}, // 不符合(空值) - {"deviceId": "sensor3", "value": 15.0, "status": "inactive"}, // 不符合(值<=20) - {"deviceId": "sensor4", "value": 30.0, "status": "error"}, // 符合条件 + {"deviceId": "sensor1", "value": 25.5, "status": "active"}, // Meet the requirements + {"deviceId": "sensor2", "value": nil, "status": "active"}, // Nonconforming (null) + {"deviceId": "sensor3", "value": 15.0, "status": "inactive"}, // Not compliant (value <=20) + {"deviceId": "sensor4", "value": 30.0, "status": "error"}, // Meet the requirements } for _, data := range testData { @@ -101,7 +101,7 @@ func demo3() { ssql := streamsql.New() defer ssql.Stop() - // 使用 = null 和 != null 语法 + // Use the = null and!= null syntax rsql := `SELECT deviceId, value, status FROM stream WHERE status != null OR value = null` @@ -124,10 +124,10 @@ func demo3() { }) testData := []map[string]any{ - {"deviceId": "sensor1", "value": 25.5, "status": "active"}, // 符合(status不为null) - {"deviceId": "sensor2", "value": nil, "status": nil}, // 符合(value为null) - {"deviceId": "sensor3", "value": 30.0, "status": "inactive"}, // 符合(status不为null) - {"deviceId": "sensor4", "value": nil, "status": "error"}, // 符合(两个条件都满足) + {"deviceId": "sensor1", "value": 25.5, "status": "active"}, // Compliance (status not null) + {"deviceId": "sensor2", "value": nil, "status": nil}, // Compliant (value is null) + {"deviceId": "sensor3", "value": 30.0, "status": "inactive"}, // Compliance (status not null) + {"deviceId": "sensor4", "value": nil, "status": "error"}, // Meets (both conditions met) } for _, data := range testData { @@ -145,7 +145,7 @@ func demo4() { ssql := streamsql.New() defer ssql.Stop() - // 混合使用 IS NULL、= nil、!= null 等语法 + // Syntax such as IS NULL, = nil,!= null is used together rsql := `SELECT deviceId, value, status, priority FROM stream WHERE (value IS NOT NULL AND value > 20) OR @@ -171,12 +171,12 @@ func demo4() { }) testData := []map[string]any{ - {"deviceId": "sensor1", "value": 25.0, "status": "active", "priority": "high"}, // 符合第一个条件 - {"deviceId": "sensor2", "value": 15.0, "status": "active", "priority": "low"}, // 不符合 - {"deviceId": "sensor3", "value": nil, "status": nil, "priority": "medium"}, // 符合第二个条件 - {"deviceId": "sensor4", "value": nil, "status": nil, "priority": nil}, // 不符合 - {"deviceId": "sensor5", "value": 30.0, "status": "inactive", "priority": nil}, // 符合第一个条件 - {"deviceId": "sensor6", "value": 10.0, "status": nil, "priority": "urgent"}, // 符合第二个条件 + {"deviceId": "sensor1", "value": 25.0, "status": "active", "priority": "high"}, // Meet the first condition + {"deviceId": "sensor2", "value": 15.0, "status": "active", "priority": "low"}, // It does not fit + {"deviceId": "sensor3", "value": nil, "status": nil, "priority": "medium"}, // Meet the second condition + {"deviceId": "sensor4", "value": nil, "status": nil, "priority": nil}, // It does not fit + {"deviceId": "sensor5", "value": 30.0, "status": "inactive", "priority": nil}, // Meet the first condition + {"deviceId": "sensor6", "value": 10.0, "status": nil, "priority": "urgent"}, // Meet the second condition } for _, data := range testData { @@ -194,7 +194,7 @@ func demo5() { ssql := streamsql.New() defer ssql.Stop() - // 嵌套字段的 null 比较 + // null comparison of nested fields rsql := `SELECT deviceId, device.location FROM stream WHERE device.location != nil` @@ -216,23 +216,23 @@ func demo5() { "device": map[string]any{ "location": "warehouse-A", }, - }, // 符合条件 + }, // Meet the requirements { "deviceId": "sensor2", "device": map[string]any{ "location": nil, }, - }, // 不符合(location为nil) + }, // Not compliant (location is nil) { "deviceId": "sensor3", "device": map[string]any{}, - }, // 不符合(location字段不存在) + }, // Does not meet (location field does not exist) { "deviceId": "sensor4", "device": map[string]any{ "location": "office-B", }, - }, // 符合条件 + }, // Meet the requirements } for _, data := range testData { diff --git a/examples/simple-custom-functions/README.md b/examples/simple-custom-functions/README.md index e51b91b..3de271b 100644 --- a/examples/simple-custom-functions/README.md +++ b/examples/simple-custom-functions/README.md @@ -1,51 +1,51 @@ -# 简单自定义函数示例 - -## 简介 - -这个示例展示了如何使用StreamSQL的插件式自定义函数系统注册和使用简单的自定义函数。 - -## 功能演示 - -- ✅ 数学函数:平方计算、华氏度转摄氏度、圆面积计算 -- ✅ 直接SQL查询模式和聚合查询模式 -- ✅ 函数管理功能:查询、分类、统计 - -## 运行方式 - -```bash -cd examples/simple-custom-functions -go run main.go -``` - -## 代码亮点 - -### 1. 简单函数注册 -```go -functions.RegisterCustomFunction( - "square", // 函数名 - functions.TypeMath, // 函数类型 - "数学函数", // 分类 - "计算平方", // 描述 - 1, 1, // 参数数量 - func(ctx *functions.FunctionContext, args []interface{}) (interface{}, error) { - val, _ := functions.ConvertToFloat64(args[0]) - return val * val, nil +# Simple custom function example + +## Introduction + +This example demonstrates how to register and use simple custom functions using StreamSQL's plugin-based custom function system. + +## Feature Demonstration + +- ✅ Mathematical functions: square calculation, Fahrenheit to Celsius, circle area calculation +- ✅ Direct SQL query mode and aggregate query mode +- ✅ Function management functions: query, classification, statistics + +## Operating Mode + +```bash +cd examples/simple-custom-functions +go run main.go +``` + +## Code Highlights + +### 1. Simple function registration +```go +functions.RegisterCustomFunction( + "square", // Function name + functions.TypeMath, // Function type + "数学函数", // Classification + "计算平方", // Description + 1, 1, // Number of parameters + func(ctx *functions.FunctionContext, args []interface{}) (interface{}, error) { + val, _ := functions.ConvertToFloat64(args[0]) + return val * val, nil }, -) -``` - -### 2. SQL中直接使用 -```sql -SELECT square(value) as squared_value FROM stream -``` - -### 3. 聚合查询 -```sql -SELECT AVG(square(value)) as avg_squared FROM stream GROUP BY device, TumblingWindow('1s') -``` - -## 适用场景 - -- 🔰 初学者入门StreamSQL自定义函数 -- 📚 学习插件式函数注册机制 -- 🧪 快速验证函数功能 \ No newline at end of file +) +``` + +### 2. SQL to use directly +```sql +SELECT square(value) as squared_value FROM stream +``` + +### 3. Aggregated queries +```sql +SELECT AVG(square(value)) as avg_squared FROM stream GROUP BY device, TumblingWindow('1s') +``` + +## Applicable Scenarios + +- 🔰 Beginner's Introduction StreamSQL Custom Functions +- 📚 Learn the plugin-based function registration mechanism +- 🧪 Quick verification function functionality diff --git a/examples/simple-custom-functions/main.go b/examples/simple-custom-functions/main.go index dd1cfee..dcc7e2e 100644 --- a/examples/simple-custom-functions/main.go +++ b/examples/simple-custom-functions/main.go @@ -14,20 +14,20 @@ func main() { fmt.Println("🚀 StreamSQL 简单自定义函数演示") fmt.Println("=================================") - // 注册一些简单的自定义函数 + // Register some simple custom functions registerSimpleFunctions() - // 演示函数在SQL中的使用 + // Demonstrating the use of functions in SQL demonstrateFunctions() fmt.Println("\n✅ 演示完成!") } -// 注册简单的自定义函数 +// Register simple custom functions func registerSimpleFunctions() { fmt.Println("\n📋 注册自定义函数...") - // 1. 数学函数:平方 + // 1. Mathematical function: square err := functions.RegisterCustomFunction( "square", functions.TypeMath, @@ -45,7 +45,7 @@ func registerSimpleFunctions() { fmt.Println(" ✓ 注册数学函数: square") } - // 2. 华氏度转摄氏度函数 + // 2. Fahrenheit to Celsius function err = functions.RegisterCustomFunction( "f_to_c", functions.TypeConversion, @@ -64,7 +64,7 @@ func registerSimpleFunctions() { fmt.Println(" ✓ 注册转换函数: f_to_c") } - // 3. 圆面积计算函数 + // 3. Circle Area Calculation Function err = functions.RegisterCustomFunction( "circle_area", functions.TypeMath, @@ -87,19 +87,19 @@ func registerSimpleFunctions() { } } -// 演示自定义函数的使用 +// Demonstrate the use of custom functions func demonstrateFunctions() { fmt.Println("\n🎯 演示自定义函数在SQL中的使用") fmt.Println("================================") - // 创建StreamSQL实例 + // Create a StreamSQL instance ssql := streamsql.New() defer ssql.Stop() - // 1. 测试简单查询(不使用窗口) + // 1. Test simple queries (without using windows) testSimpleQuery(ssql) - // 2. 测试聚合查询(使用窗口) + // 2. Test aggregation queries (using the window) testAggregateQuery(ssql) } @@ -121,30 +121,30 @@ func testSimpleQuery(ssql *streamsql.Streamsql) { return } - // 添加结果监听器 + // Add a result listener ssql.AddSink(func(result []map[string]any) { fmt.Printf(" 📊 简单查询结果: %v\n", result) }) - // 添加测试数据 + // Add test data testData := []map[string]any{ { "device": "sensor1", "value": 5.0, - "temperature": 68.0, // 华氏度 + "temperature": 68.0, // The degree of the Fahrenheit degree "radius": 3.0, }, { "device": "sensor2", "value": 10.0, - "temperature": 86.0, // 华氏度 + "temperature": 86.0, // The degree of the Fahrenheit degree "radius": 2.5, }, } for _, data := range testData { ssql.Emit(data) - time.Sleep(200 * time.Millisecond) // 稍微延迟 + time.Sleep(200 * time.Millisecond) // A slight delay } time.Sleep(500 * time.Millisecond) @@ -170,12 +170,12 @@ func testAggregateQuery(ssql *streamsql.Streamsql) { return } - // 添加结果监听器 + // Add a result listener ssql.AddSink(func(result []map[string]any) { fmt.Printf(" 📊 聚合查询结果: %v\n", result) }) - // 添加测试数据 + // Add test data testData := []map[string]any{ { "device": "sensor1", @@ -201,14 +201,14 @@ func testAggregateQuery(ssql *streamsql.Streamsql) { ssql.Emit(data) } - // 等待窗口触发 + // Wait for the window to trigger time.Sleep(1 * time.Second) ssql.Stream().Window.Trigger() time.Sleep(500 * time.Millisecond) fmt.Println(" ✅ 聚合查询测试完成") - // 展示函数管理功能 + // Showcase function management features showFunctionManagement() } @@ -216,21 +216,21 @@ func showFunctionManagement() { fmt.Println("\n🔧 函数管理功能演示") fmt.Println("==================") - // 列出所有数学函数 + // List all mathematical functions fmt.Println("\n📊 数学函数:") mathFunctions := functions.GetByType(functions.TypeMath) for _, fn := range mathFunctions { fmt.Printf(" • %s - %s\n", fn.GetName(), fn.GetDescription()) } - // 列出所有字符串函数 + // List all string functions fmt.Println("\n📝 字符串函数:") stringFunctions := functions.GetByType(functions.TypeString) for _, fn := range stringFunctions { fmt.Printf(" • %s - %s\n", fn.GetName(), fn.GetDescription()) } - // 检查特定函数是否存在 + // Check whether a specific function exists fmt.Println("\n🔍 函数查找:") if fn, exists := functions.Get("square"); exists { fmt.Printf(" ✓ 找到函数: %s (%s)\n", fn.GetName(), fn.GetDescription()) @@ -240,12 +240,12 @@ func showFunctionManagement() { fmt.Printf(" ✓ 找到函数: %s (%s)\n", fn.GetName(), fn.GetDescription()) } - // 统计函数数量 + // Count the number of functions allFunctions := functions.ListAll() fmt.Printf("\n📈 统计信息:\n") fmt.Printf(" • 总函数数量: %d\n", len(allFunctions)) - // 按类型统计 + // Statistics by type typeCount := make(map[functions.FunctionType]int) for _, fn := range allFunctions { typeCount[fn.GetType()]++ diff --git a/examples/table_print_demo/main.go b/examples/table_print_demo/main.go index 9564ae8..f72ac42 100644 --- a/examples/table_print_demo/main.go +++ b/examples/table_print_demo/main.go @@ -7,14 +7,14 @@ import ( "github.com/rulego/streamsql" ) -// main 演示PrintTable方法的使用 +// main demonstrates the use of the PrintTable method func main() { fmt.Println("=== StreamSQL PrintTable 示例 ===") - // 创建StreamSQL实例 + // Create a StreamSQL instance ssql := streamsql.New() - // 示例1: 聚合查询 - 按设备分组统计温度 + // Example 1: Aggregated query - grouping temperature statistics by device fmt.Println("\n示例1: 聚合查询结果") err := ssql.Execute("SELECT device, AVG(temperature) as avg_temp, MAX(temperature) as max_temp FROM stream GROUP BY device, TumblingWindow('3s')") if err != nil { @@ -22,10 +22,10 @@ func main() { return } - // 使用PrintTable方法以表格形式输出结果 + // Use the PrintTable method to output results in table format ssql.PrintTable() - // 发送测试数据 + // Send test data testData := []map[string]any{ {"device": "sensor1", "temperature": 25.5, "timestamp": time.Now()}, {"device": "sensor1", "temperature": 26.0, "timestamp": time.Now()}, @@ -38,10 +38,10 @@ func main() { ssql.Emit(data) } - // 等待窗口触发 + // Wait for the window to trigger time.Sleep(4 * time.Second) - // 示例2: 非聚合查询 + // Example 2: Non-aggregated queries fmt.Println("\n示例2: 非聚合查询结果") ssql2 := streamsql.New() err = ssql2.Execute("SELECT device, temperature, temperature * 1.8 + 32 as fahrenheit FROM stream WHERE temperature > 24") @@ -52,15 +52,15 @@ func main() { ssql2.PrintTable() - // 发送测试数据 + // Send test data for _, data := range testData { ssql2.Emit(data) } - // 等待处理完成 + // Wait for processing to complete time.Sleep(1 * time.Second) - // 示例3: 对比原始Print方法 + // Example 3: Compare with the original print method fmt.Println("\n示例3: 原始Print方法输出对比") ssql3 := streamsql.New() err = ssql3.Execute("SELECT device, COUNT(*) as count FROM stream GROUP BY device, TumblingWindow('2s')") @@ -72,7 +72,7 @@ func main() { fmt.Println("原始PrintTable方法:") ssql3.PrintTable() - // 发送数据 + // Send data for i := 0; i < 3; i++ { ssql3.Emit(map[string]any{"device": "test_device", "value": i}) } diff --git a/expr/case_expression_test.go b/expr/case_expression_test.go index 5e2b890..2079da1 100644 --- a/expr/case_expression_test.go +++ b/expr/case_expression_test.go @@ -7,7 +7,7 @@ import ( "github.com/stretchr/testify/require" ) -// TestEvaluateCaseExpression 测试CASE表达式求值 +// TestEvaluateCaseExpression evaluates the CASE expression func TestEvaluateCaseExpression(t *testing.T) { tests := []struct { name string @@ -76,7 +76,7 @@ func TestEvaluateCaseExpression(t *testing.T) { } } -// TestEvaluateCaseExpressionWithNull 测试支持NULL的CASE表达式求值 +// TestEvaluateCaseExpressionWithNull tests support evaluating NULL's CASE expressions func TestEvaluateCaseExpressionWithNull(t *testing.T) { tests := []struct { name string diff --git a/expr/evaluator_test.go b/expr/evaluator_test.go index d68d4a4..1dc7eb3 100644 --- a/expr/evaluator_test.go +++ b/expr/evaluator_test.go @@ -8,7 +8,7 @@ import ( "github.com/stretchr/testify/require" ) -// TestEvaluateNode 测试节点求值功能 +// TestEvaluateNode is a test node evaluation feature func TestEvaluateNode(t *testing.T) { data := map[string]any{ "a": 10.0, @@ -115,7 +115,7 @@ func TestEvaluateNode(t *testing.T) { 15.0, false, }, - // 错误情况 + // Error case {"不存在的字段", &ExprNode{Type: TypeField, Value: "unknown"}, 0, true}, {"除零错误", &ExprNode{ Type: TypeOperator, @@ -144,7 +144,7 @@ func TestEvaluateNode(t *testing.T) { } } -// TestEvaluateNodeWithNull 测试支持NULL值的节点求值 +// TestEvaluateNodeWithNull tests support node evaluation of NULL values func TestEvaluateNodeWithNull(t *testing.T) { data := map[string]any{ "a": 10.0, @@ -298,7 +298,7 @@ func TestEvaluateNodeWithNull(t *testing.T) { false, false, }, - // 错误情况 + // Error case { "无效数字", &ExprNode{Type: TypeNumber, Value: "invalid"}, @@ -360,7 +360,7 @@ func TestEvaluateNodeWithNull(t *testing.T) { } } -// TestEvaluateIsOperator 测试IS运算符 +// TestEvaluateIsOperator tests the IS operator func TestEvaluateIsOperator(t *testing.T) { data := map[string]any{ "a": 10.0, @@ -474,7 +474,7 @@ func TestEvaluateIsOperator(t *testing.T) { true, false, }, - // 错误情况 + // Error case { "缺少右操作数", &ExprNode{ @@ -512,7 +512,7 @@ func TestEvaluateIsOperator(t *testing.T) { } } -// TestEvaluateBoolFunction 测试布尔函数求值 +// TestEvaluateBoolFunction to evaluate the Boolean function func TestEvaluateBoolFunction(t *testing.T) { data := map[string]any{} @@ -542,7 +542,7 @@ func TestEvaluateBoolFunction(t *testing.T) { false, false, }, - // 错误情况 + // Error case { "未知函数", &ExprNode{ @@ -568,7 +568,7 @@ func TestEvaluateBoolFunction(t *testing.T) { } } -// TestEvaluateFieldNode 测试字段节点求值 +// TestEvaluateFieldNode Evaluates the field node func TestEvaluateFieldNode(t *testing.T) { data := map[string]any{ "int_field": 42, @@ -586,7 +586,7 @@ func TestEvaluateFieldNode(t *testing.T) { }{ {"整数字段", "int_field", 42.0, false}, {"浮点数字段", "float_field", 3.14, false}, - {"字符串字段(数字)", "string_field", 0, true}, // 字符串"hello"无法转换为数字 + {"字符串字段(数字)", "string_field", 0, true}, // The string "hello" cannot be converted to numbers {"布尔字段", "bool_field", 1.0, false}, {"nil字段", "nil_field", 0, true}, {"不存在的字段", "unknown_field", 0, true}, @@ -606,7 +606,7 @@ func TestEvaluateFieldNode(t *testing.T) { } } -// TestEvaluateOperatorNode 测试运算符节点求值 +// TestEvaluateOperatorNode Evaluates the node of the test operator func TestEvaluateOperatorNode(t *testing.T) { data := map[string]any{ "a": 10.0, @@ -670,7 +670,7 @@ func TestEvaluateOperatorNode(t *testing.T) { 9.0, false, }, - // 错误情况 + // Error case { "除零", "/", @@ -716,7 +716,7 @@ func TestEvaluateOperatorNode(t *testing.T) { } } -// TestEvaluateFunctionNode 测试函数节点求值 +// TestEvaluateFunctionNode Evaluates the function node func TestEvaluateFunctionNode(t *testing.T) { data := map[string]any{} @@ -827,7 +827,7 @@ func TestEvaluateFunctionNode(t *testing.T) { 4.0, false, }, - // 三角函数 + // Trigonometric functions { "SIN函数", "sin", @@ -842,7 +842,7 @@ func TestEvaluateFunctionNode(t *testing.T) { 1.0, false, }, - // 对数函数 + // Logarithmic function { "LOG函数", "log", @@ -864,7 +864,7 @@ func TestEvaluateFunctionNode(t *testing.T) { 1.0, false, }, - // 错误情况 + // Error case {"未知函数", "unknown", []*ExprNode{{Type: TypeNumber, Value: "1"}}, 0, true}, {"参数数量错误", "abs", []*ExprNode{}, 0, true}, {"SQRT负数", "sqrt", []*ExprNode{{Type: TypeNumber, Value: "-1"}}, 0, true}, @@ -889,7 +889,7 @@ func TestEvaluateFunctionNode(t *testing.T) { } } -// TestEvaluateNodeValue 测试节点值求值(支持NULL) +// TestEvaluateNodeValue Evaluate the test node value (supports NULL) func TestEvaluateNodeValue(t *testing.T) { data := map[string]any{ "a": 10.0, @@ -998,7 +998,7 @@ func TestEvaluateNodeValue(t *testing.T) { false, false, }, - // 错误情况 + // Error case {"不存在的字段", &ExprNode{Type: TypeField, Value: "unknown"}, nil, true}, } @@ -1015,7 +1015,7 @@ func TestEvaluateNodeValue(t *testing.T) { } } -// TestCompareValues 测试值比较功能 +// TestCompareValues test value comparison feature func TestCompareValues(t *testing.T) { tests := []struct { name string @@ -1025,7 +1025,7 @@ func TestCompareValues(t *testing.T) { expected bool wantErr bool }{ - // 数字比较 + // Comparison of numbers {"数字相等", "==", 5.0, 5.0, true, false}, {"数字不等", "!=", 5.0, 3.0, true, false}, {"数字大于", ">", 5.0, 3.0, true, false}, @@ -1033,25 +1033,25 @@ func TestCompareValues(t *testing.T) { {"数字大于等于", ">=", 5.0, 5.0, true, false}, {"数字小于等于", "<=", 3.0, 5.0, true, false}, - // 字符串比较 + // String comparison {"字符串相等", "==", "hello", "hello", true, false}, {"字符串不等", "!=", "hello", "world", true, false}, {"字符串大于", ">", "world", "hello", true, false}, {"字符串小于", "<", "hello", "world", true, false}, - // LIKE模式匹配 + // LIKE pattern matching {"LIKE匹配", "LIKE", "hello", "h%", true, false}, {"LIKE不匹配", "LIKE", "hello", "w%", false, false}, {"LIKE通配符", "LIKE", "hello", "h_llo", true, false}, {"LIKE完全匹配", "LIKE", "hello", "hello", true, false}, - // 混合类型比较 + // Mixed types comparison {"数字与字符串", "==", 5.0, "5", true, false}, {"布尔值比较", "==", true, true, true, false}, {"布尔值与数字", "==", true, 1.0, true, false}, {"布尔值与数字(假)", "==", false, 0.0, true, false}, - // 错误情况 + // Error case {"无效运算符", "@", 5.0, 3.0, false, true}, {"不兼容类型", ">", "hello", 5.0, false, true}, } @@ -1069,7 +1069,7 @@ func TestCompareValues(t *testing.T) { } } -// TestMatchLikePattern 测试LIKE模式匹配 +// TestMatchLikePattern Tests LIKE pattern matching func TestMatchLikePattern(t *testing.T) { tests := []struct { name string @@ -1101,7 +1101,7 @@ func TestMatchLikePattern(t *testing.T) { } } -// TestEvaluateBoolNode 测试布尔节点求值 +// TestEvaluateBoolNode tests the Boolean node's evaluation func TestEvaluateBoolNode(t *testing.T) { data := map[string]any{ "a": 10.0, @@ -1209,7 +1209,7 @@ func TestEvaluateBoolNode(t *testing.T) { true, false, }, - // 新增测试用例以提高覆盖率 + // Add test cases to increase coverage { "字段节点(真值)", &ExprNode{Type: TypeField, Value: "flag"}, @@ -1271,7 +1271,7 @@ func TestEvaluateBoolNode(t *testing.T) { true, false, }, - // 错误情况 + // Error case {"非布尔运算符", &ExprNode{ Type: TypeOperator, Value: "+", diff --git a/expr/expression_test.go b/expr/expression_test.go index cdb16b6..abc32ed 100644 --- a/expr/expression_test.go +++ b/expr/expression_test.go @@ -203,7 +203,7 @@ func TestCaseExpressionParsing(t *testing.T) { } } -// TestCaseExpressionFieldExtraction 测试CASE表达式的字段提取功能 +// TestCaseExpressionFieldExtraction tests the field extraction function of CASE expressions func TestCaseExpressionFieldExtraction(t *testing.T) { testCases := []struct { name string @@ -239,7 +239,7 @@ func TestCaseExpressionFieldExtraction(t *testing.T) { fields := expression.GetFields() - // 验证所有期望的字段都被提取到了 + // All the desired fields were extracted for verification for _, expectedField := range tc.expectedFields { assert.Contains(t, fields, expectedField, "应该包含字段: %s", expectedField) } @@ -247,13 +247,13 @@ func TestCaseExpressionFieldExtraction(t *testing.T) { } } -// TestCaseExpressionWithNullComparisons 测试CASE表达式中的NULL比较 +// TestCaseExpressionWithNullComparisons: Tests the NULL comparison in the CASE expression func TestCaseExpressionWithNullComparisons(t *testing.T) { tests := []struct { name string exprStr string data map[string]any - expected any // 使用any以支持NULL值 + expected any // Use any to support NULL values isNull bool }{ { @@ -298,7 +298,7 @@ func TestCaseExpressionWithNullComparisons(t *testing.T) { expression, err := NewExpression(tt.exprStr) assert.NoError(t, err, "表达式解析应该成功") - // 测试支持NULL的计算方法 + // Testing the computational methods that support NULL result, isNull, err := expression.EvaluateWithNull(tt.data) assert.NoError(t, err, "表达式计算应该成功") @@ -312,7 +312,7 @@ func TestCaseExpressionWithNullComparisons(t *testing.T) { } } -// TestNegativeNumberSupport 专门测试负数支持 +// TestNegativeNumberSupport is specifically designed to test negative number support func TestNegativeNumberSupport(t *testing.T) { tests := []struct { name string @@ -370,7 +370,7 @@ func TestNegativeNumberSupport(t *testing.T) { expected: 1.0, wantErr: false, }, - // 基本负数运算 + // Basic negative number operations { name: "直接负数", exprStr: "-5", @@ -405,7 +405,7 @@ func TestNegativeNumberSupport(t *testing.T) { assert.NoError(t, err, "负数表达式解析应该成功") assert.NotNil(t, expression, "表达式不应为空") - // 测试表达式计算 + // Test expression calculation result, err := expression.Evaluate(tt.data) assert.NoError(t, err, "负数表达式计算应该成功") assert.Equal(t, tt.expected, result, "负数表达式结果应该匹配期望值") @@ -432,7 +432,7 @@ func TestGetFields(t *testing.T) { fields := expr.GetFields() - // 由于map迭代顺序不确定,我们只检查长度和包含关系 + // Since the map iteration order is uncertain, we only check the length and inclusion relationships assert.Equal(t, len(tt.expectedFields), len(fields), "Number of fields should match") for _, field := range tt.expectedFields { @@ -468,7 +468,7 @@ func TestParseError(t *testing.T) { } } -// TestExpressionTokenization 测试表达式分词功能 +// TestExpressionTokenization tests the word segmentation function of expressions func TestExpressionTokenization(t *testing.T) { tests := []struct { name string @@ -492,7 +492,7 @@ func TestExpressionTokenization(t *testing.T) { } } -// TestExpressionValidation 测试表达式验证功能 +// TestExpressionValidation Function of test expressions func TestExpressionValidation(t *testing.T) { tests := []struct { name string @@ -524,7 +524,7 @@ func TestExpressionValidation(t *testing.T) { } } -// TestExpressionOperatorPrecedence 测试运算符优先级 +// TestExpressionOperatorPrecedence The test operator precedence func TestExpressionOperatorPrecedence(t *testing.T) { tests := []struct { name string @@ -551,7 +551,7 @@ func TestExpressionOperatorPrecedence(t *testing.T) { } } -// TestExpressionFunctions 测试内置函数 +// TestExpressionFunctions tests built-in functions func TestExpressionFunctions(t *testing.T) { tests := []struct { name string @@ -600,7 +600,7 @@ func TestExpressionFunctions(t *testing.T) { } } -// TestExpressionDataTypeConversion 测试数据类型转换 +// TestExpressionDataTypeConversion Tests data type conversion func TestExpressionDataTypeConversion(t *testing.T) { tests := []struct { name string @@ -635,7 +635,7 @@ func TestExpressionDataTypeConversion(t *testing.T) { } } -// TestExpressionEdgeCases 测试边界情况 +// TestExpressionEdgeCases tests boundary conditions func TestExpressionEdgeCases(t *testing.T) { tests := []struct { name string @@ -675,12 +675,12 @@ func TestExpressionEdgeCases(t *testing.T) { } } -// TestExpressionConcurrency 测试并发安全性 +// TestExpressionConcurrency tests concurrency security func TestExpressionConcurrency(t *testing.T) { expr, err := NewExpression("a + b * c") require.NoError(t, err, "Expression parsing should not fail") - // 并发执行多个计算 + // Multiple computations are performed concurrently const numGoroutines = 100 results := make(chan float64, numGoroutines) @@ -697,16 +697,16 @@ func TestExpressionConcurrency(t *testing.T) { }(i) } - // 收集结果 + // Collect the results for i := 0; i < numGoroutines; i++ { result := <-results - // 验证结果是合理的(非零且非NaN) + // Verification results are reasonable (non-zero and non-NaN) assert.False(t, math.IsNaN(result), "Result should not be NaN") assert.True(t, result >= 0, "Result should be non-negative for this test") } } -// TestExpressionComplexNesting 测试复杂嵌套表达式 +// TestExpressionComplexNesting Tests complex nested expressions func TestExpressionComplexNesting(t *testing.T) { tests := []struct { name string @@ -746,7 +746,7 @@ func TestExpressionComplexNesting(t *testing.T) { } } -// TestExpressionStringHandling 测试字符串处理 +// TestExpressionStringHandling test string processing func TestExpressionStringHandling(t *testing.T) { tests := []struct { name string @@ -779,9 +779,9 @@ func TestExpressionStringHandling(t *testing.T) { } } -// TestExpressionPerformance 测试表达式性能 +// TestExpressionPerformance Tests the performance of the expression func TestExpressionPerformance(t *testing.T) { - // 创建一个复杂表达式 + // Create a complex expression expr, err := NewExpression("sqrt(pow(a, 2) + pow(b, 2)) + abs(c - d) * (e + f) / (g + 1)") require.NoError(t, err, "Expression parsing should not fail") @@ -789,7 +789,7 @@ func TestExpressionPerformance(t *testing.T) { "a": 3.0, "b": 4.0, "c": 10.0, "d": 7.0, "e": 2.0, "f": 3.0, "g": 4.0, } - // 执行多次计算以测试性能 + // Perform multiple calculations to test performance const iterations = 10000 for i := 0; i < iterations; i++ { _, err := expr.Evaluate(data) @@ -797,9 +797,9 @@ func TestExpressionPerformance(t *testing.T) { } } -// TestExpressionMemoryUsage 测试内存使用 +// TestExpressionMemoryUsage tests memory usage func TestExpressionMemoryUsage(t *testing.T) { - // 创建多个表达式实例 + // Create multiple expression instances const numExpressions = 1000 expressions := make([]*Expression, numExpressions) @@ -809,7 +809,7 @@ func TestExpressionMemoryUsage(t *testing.T) { expressions[i] = expr } - // 验证所有表达式都能正常工作 + // Verify that all expressions work properly data := map[string]any{"a": 1, "b": 2, "c": 3} for i, expr := range expressions { result, err := expr.Evaluate(data) @@ -1160,12 +1160,12 @@ func TestEvaluateValueWithNull(t *testing.T) { name: "missing field", expr: "field1 + field2", data: map[string]any{"field1": 1}, - expectNull: false, // 实际行为:返回nil但isNull为false + expectNull: false, // Actual behavior: returns nil but isNull is false expectError: false, }, { name: "invalid expression", - expr: "field1 + field2 +", // 使用无效语法 + expr: "field1 + field2 +", // Use invalid grammar data: map[string]any{}, expectNull: false, expectError: true, @@ -1177,7 +1177,7 @@ func TestEvaluateValueWithNull(t *testing.T) { expr, err := NewExpression(tt.expr) if err != nil { if tt.expectError { - // 如果期望错误,那么创建表达式失败是正常的 + // If the expectation is incorrect, then failure to create the expression is normal return } t.Errorf("failed to create expression: %v", err) @@ -1196,7 +1196,7 @@ func TestEvaluateValueWithNull(t *testing.T) { if !tt.expectError && isNull != tt.expectNull { t.Errorf("expected null=%v, got null=%v", tt.expectNull, isNull) } - // 对于缺失字段的情况,允许result为nil + // For missing fields, result is allowed to be nil if !tt.expectError && !tt.expectNull && result == nil && tt.name != "missing field" { t.Error("expected non-nil result but got nil") } diff --git a/expr/parser_test.go b/expr/parser_test.go index 6786cba..c1bd42e 100644 --- a/expr/parser_test.go +++ b/expr/parser_test.go @@ -7,7 +7,7 @@ import ( "github.com/stretchr/testify/require" ) -// TestParseExpression 测试表达式解析功能 +// TestParseExpression parsing function for test expressions func TestParseExpression(t *testing.T) { tests := []struct { name string @@ -124,12 +124,12 @@ func TestParseUnaryExpression(t *testing.T) { } } -// TestParseExpressionWithPrecedence 测试运算符优先级解析 +// TestParseExpressionWithPrecedence The test operator prioritizes resolution func TestParseExpressionWithPrecedence(t *testing.T) { tests := []struct { name string tokens []string - expected string // 用字符串表示预期的树结构 + expected string // Represent the expected tree structure with strings }{ {"加法和乘法", []string{"a", "+", "b", "*", "c"}, "(a + (b * c))"}, {"乘法和除法", []string{"a", "*", "b", "/", "c"}, "((a * b) / c)"}, @@ -148,7 +148,7 @@ func TestParseExpressionWithPrecedence(t *testing.T) { } } -// TestParseFunction 测试函数解析 +// TestParseFunction Test function analysis func TestParseFunction(t *testing.T) { tests := []struct { name string @@ -215,7 +215,7 @@ func TestParseFunction(t *testing.T) { 9, false, }, - // 错误情况 + // Error case {"缺少左括号", []string{"abs", "x", ")"}, 0, nil, 0, true}, {"缺少右括号", []string{"abs", "(", "x"}, 0, nil, 0, true}, {"参数分隔符错误", []string{"max", "(", "a", ";", "b", ")"}, 0, nil, 0, true}, @@ -235,7 +235,7 @@ func TestParseFunction(t *testing.T) { } } -// TestGetOperatorPrecedence 测试运算符优先级获取 +// TestGetOperatorPrecedence Determines the precedence of the test operator func TestGetOperatorPrecedence(t *testing.T) { tests := []struct { op string @@ -271,7 +271,7 @@ func TestGetOperatorPrecedence(t *testing.T) { } } -// TestIsRightAssociative 测试右结合性判断 +// TestIsRightAssociative TestIsAssociative Assessment of Right Association func TestIsRightAssociative(t *testing.T) { tests := []struct { op string @@ -295,7 +295,7 @@ func TestIsRightAssociative(t *testing.T) { } } -// 辅助函数:连接字符串数组 +// Auxiliary function: concatenate string arrays func joinStrings(strs []string, sep string) string { if len(strs) == 0 { return "" diff --git a/expr/tokenizer_custom_test.go b/expr/tokenizer_custom_test.go index fc5e105..b9e0b68 100644 --- a/expr/tokenizer_custom_test.go +++ b/expr/tokenizer_custom_test.go @@ -6,7 +6,7 @@ import ( "github.com/stretchr/testify/assert" ) -// TestTokenizeWithArrayAccess 测试包含数组访问的分词 +// TestTokenizeWithArrayAccess tests the segmentation of array access func TestTokenizeWithArrayAccess(t *testing.T) { tests := []struct { name string diff --git a/expr/tokenizer_test.go b/expr/tokenizer_test.go index 12c8308..c1d4471 100644 --- a/expr/tokenizer_test.go +++ b/expr/tokenizer_test.go @@ -7,7 +7,7 @@ import ( "github.com/stretchr/testify/require" ) -// TestTokenize 测试分词功能 +// TestTokenize tests word segmentation functionality func TestTokenize(t *testing.T) { tests := []struct { name string @@ -15,49 +15,49 @@ func TestTokenize(t *testing.T) { expected []string wantErr bool }{ - // 基本分词测试 + // Basic word segmentation test {"简单表达式", "a + b", []string{"a", "+", "b"}, false}, {"数字和运算符", "123 + 456", []string{"123", "+", "456"}, false}, {"小数", "3.14 * 2", []string{"3.14", "*", "2"}, false}, {"负数", "-5 + 3", []string{"-5", "+", "3"}, false}, {"负小数", "-3.14 * 2", []string{"-3.14", "*", "2"}, false}, - // 括号和函数 + // Parentheses and functions {"括号表达式", "(a + b) * c", []string{"(", "a", "+", "b", ")", "*", "c"}, false}, {"函数调用", "abs(x)", []string{"abs", "(", "x", ")"}, false}, {"函数参数", "max(a, b)", []string{"max", "(", "a", ",", "b", ")"}, false}, - // 比较运算符 + // Comparison operator {"等于运算符", "a == b", []string{"a", "==", "b"}, false}, {"不等于运算符", "a != b", []string{"a", "!=", "b"}, false}, {"大于等于", "a >= b", []string{"a", ">=", "b"}, false}, {"小于等于", "a <= b", []string{"a", "<=", "b"}, false}, {"不等于SQL风格", "a <> b", []string{"a", "<>", "b"}, false}, - // 字符串字面量 + // string literal {"单引号字符串", "'hello'", []string{"'hello'"}, false}, {"双引号字符串", "\"world\"", []string{"\"world\""}, false}, {"字符串比较", "name == 'test'", []string{"name", "==", "'test'"}, false}, {"包含转义的字符串", "'hello\\world'", []string{"'hello\\world'"}, false}, - // 反引号标识符 + // Backquote identifier {"反引号字段", "`field name`", []string{"`field name`"}, false}, {"反引号表达式", "`user.name` + `user.age`", []string{"`user.name`", "+", "`user.age`"}, false}, - // CASE表达式 + // CASE expression {"简单CASE", "CASE WHEN a > 0 THEN 1 ELSE 0 END", []string{"CASE", "WHEN", "a", ">", "0", "THEN", "1", "ELSE", "0", "END"}, false}, - // 复杂表达式 + // Complex expressions {"复杂算术", "a + b * c - d / e", []string{"a", "+", "b", "*", "c", "-", "d", "/", "e"}, false}, {"幂运算", "a ^ b", []string{"a", "^", "b"}, false}, {"取模运算", "a % b", []string{"a", "%", "b"}, false}, - // 空白字符处理 + // Whitespace character processing {"多个空格", "a + b", []string{"a", "+", "b"}, false}, {"制表符", "a\t+\tb", []string{"a", "+", "b"}, false}, {"换行符", "a\n+\nb", []string{"a", "+", "b"}, false}, - // 错误情况 + // Error case {"空表达式", "", nil, true}, {"只有空格", " ", nil, true}, {"未闭合字符串", "'hello", nil, true}, @@ -78,7 +78,7 @@ func TestTokenize(t *testing.T) { } } -// TestIsDigit 测试数字字符判断 +// TestIsDigit tests numeric character judgments func TestIsDigit(t *testing.T) { tests := []struct { ch byte @@ -102,7 +102,7 @@ func TestIsDigit(t *testing.T) { } } -// TestIsLetter 测试字母字符判断 +// TestIsLetter tests the character for letters func TestIsLetter(t *testing.T) { tests := []struct { ch byte @@ -127,7 +127,7 @@ func TestIsLetter(t *testing.T) { } } -// TestIsNumber 测试数字字符串判断 +// TestIsNumber tests for numeric string checks func TestIsNumber(t *testing.T) { tests := []struct { s string @@ -155,7 +155,7 @@ func TestIsNumber(t *testing.T) { } } -// TestIsIdentifier 测试标识符判断 +// TestIsIdentifier Test the identifier to check func TestIsIdentifier(t *testing.T) { tests := []struct { s string @@ -183,7 +183,7 @@ func TestIsIdentifier(t *testing.T) { } } -// TestIsOperator 测试运算符判断 +// TestIsOperator test operator to check func TestIsOperator(t *testing.T) { tests := []struct { s string @@ -222,7 +222,7 @@ func TestIsOperator(t *testing.T) { } } -// TestIsComparisonOperator 测试比较运算符判断 +// TestIsComparisonOperator tests the comparison operator's judgment func TestIsComparisonOperator(t *testing.T) { tests := []struct { s string @@ -252,7 +252,7 @@ func TestIsComparisonOperator(t *testing.T) { } } -// TestIsStringLiteral 测试字符串字面量判断 +// TestIsStringLiteral: Tests string literal quantity check func TestIsStringLiteral(t *testing.T) { tests := []struct { s string @@ -280,7 +280,7 @@ func TestIsStringLiteral(t *testing.T) { } } -// TestTokenizeComplexExpressions 测试复杂表达式分词 +// TestTokenizeComplexExpressions tests complex expression segmentation func TestTokenizeComplexExpressions(t *testing.T) { tests := []struct { name string @@ -323,7 +323,7 @@ func TestTokenizeComplexExpressions(t *testing.T) { } } -// TestTokenizeEdgeCases 测试边界情况 +// TestTokenizeEdgeCases tests boundary conditions func TestTokenizeEdgeCases(t *testing.T) { tests := []struct { name string @@ -334,7 +334,7 @@ func TestTokenizeEdgeCases(t *testing.T) { {"只有数字", "123", []string{"123"}, false}, {"只有小数点开头的数字", ".5", []string{".5"}, false}, {"连续运算符(应该在解析阶段检测)", "a + + b", []string{"a", "+", "+", "b"}, false}, - {"多个小数点", "3.14.15", []string{"3.14", ".", "15"}, false}, // 分词器不检查语法错误 + {"多个小数点", "3.14.15", []string{"3.14", ".", "15"}, false}, // The word segmentation does not check for grammatical errors {"空字符串转义", "''", []string{"''"}, false}, {"包含空格的反引号标识符", "`user name`", []string{"`user name`"}, false}, {"特殊字符在字符串中", "'hello@world#test'", []string{"'hello@world#test'"}, false}, diff --git a/expr/utils_test.go b/expr/utils_test.go index af8a466..fe15695 100644 --- a/expr/utils_test.go +++ b/expr/utils_test.go @@ -6,7 +6,7 @@ import ( "github.com/stretchr/testify/assert" ) -// TestUnquoteString 测试字符串去引号 +// TestUnquoteString Removes quotes from the test string func TestUnquoteString(t *testing.T) { tests := []struct { name string @@ -34,7 +34,7 @@ func TestUnquoteString(t *testing.T) { } } -// TestUnquoteBacktick 测试反引号去除 +// TestUnquoteBacktick tests for the removal of backquotes func TestUnquoteBacktick(t *testing.T) { tests := []struct { name string @@ -59,7 +59,7 @@ func TestUnquoteBacktick(t *testing.T) { } } -// TestFormatError 测试错误格式化 +// TestFormatError test error formatting func TestFormatError(t *testing.T) { tests := []struct { name string @@ -82,7 +82,7 @@ func TestFormatError(t *testing.T) { } } -// TestCopyNode 测试节点复制 +// TestCopyNode Copies test nodes func TestCopyNode(t *testing.T) { tests := []struct { name string @@ -134,30 +134,30 @@ func TestCopyNode(t *testing.T) { t.Run(tt.name, func(t *testing.T) { copied := copyNode(tt.node) - // 检查复制的节点不是同一个对象 + // Check that the replicated node is not the same object assert.NotSame(t, tt.node, copied, "复制的节点应该是不同的对象") - // 检查值是否相等 + // Check if the values are equal assertNodeEqual(t, tt.node, copied) - // 修改原节点,确保复制的节点不受影响 + // Modify the original node to ensure the copied node is not affected originalValue := tt.node.Value tt.node.Value = "modified" assert.NotEqual(t, tt.node.Value, copied.Value, "修改原节点不应该影响复制的节点") - // 恢复原值 + // Restore the original value tt.node.Value = originalValue }) } } -// TestCopyNode_Nil 测试空节点复制 +// TestCopyNode_Nil Test the null node copy func TestCopyNode_Nil(t *testing.T) { result := copyNode(nil) assert.Nil(t, result, "复制nil节点应该返回nil") } -// TestGetNodeType 测试获取节点类型 +// TestGetNodeType Tests to get the node type func TestGetNodeType(t *testing.T) { tests := []struct { name string @@ -183,7 +183,7 @@ func TestGetNodeType(t *testing.T) { } } -// TestGetNodeValue 测试获取节点值 +// TestGetNodeValue tests to obtain the node value func TestGetNodeValue(t *testing.T) { tests := []struct { name string @@ -204,7 +204,7 @@ func TestGetNodeValue(t *testing.T) { } } -// TestSetNodeValue 测试设置节点值 +// TestSetNodeValue tests the node values func TestSetNodeValue(t *testing.T) { tests := []struct { name string @@ -224,15 +224,15 @@ func TestSetNodeValue(t *testing.T) { } } -// TestSetNodeValue_Nil 测试设置空节点值 +// TestSetNodeValue_Nil Test setting of the null node value func TestSetNodeValue_Nil(t *testing.T) { - // 这应该不会panic + // This shouldn't be a panic assert.NotPanics(t, func() { setNodeValue(nil, "test") }, "设置nil节点值不应该panic") } -// TestIsArithmeticOperator 测试算术运算符判断 +// TestIsArithmeticOperator tests the arithmetic operator's judgment func TestIsArithmeticOperator(t *testing.T) { tests := []struct { operator string @@ -261,7 +261,7 @@ func TestIsArithmeticOperator(t *testing.T) { } } -// TestIsLogicalOperator 测试逻辑运算符判断 +// TestIsLogicalOperator checks the logical operator func TestIsLogicalOperator(t *testing.T) { tests := []struct { operator string @@ -287,7 +287,7 @@ func TestIsLogicalOperator(t *testing.T) { } } -// TestIsUnaryOperator 测试一元运算符判断 +// TestIsUnaryOperator tests the unary operator's check func TestIsUnaryOperator(t *testing.T) { tests := []struct { operator string @@ -310,7 +310,7 @@ func TestIsUnaryOperator(t *testing.T) { } } -// TestIsKeyword 测试关键字判断 +// TestIsKeyword to determine the test keyword func TestIsKeyword(t *testing.T) { tests := []struct { word string @@ -329,12 +329,12 @@ func TestIsKeyword(t *testing.T) { {"NULL", true}, {"TRUE", true}, {"FALSE", true}, - // 大小写测试 + // Case Test {"case", true}, {"Case", true}, {"when", true}, {"and", true}, - // 非关键字 + // Non-keywords {"field", false}, {"value", false}, {"123", false}, @@ -350,7 +350,7 @@ func TestIsKeyword(t *testing.T) { } } -// TestNormalizeIdentifier 测试标识符规范化 +// TestNormalizeIdentifier Standardizes the test identifier func TestNormalizeIdentifier(t *testing.T) { tests := []struct { name string @@ -374,7 +374,7 @@ func TestNormalizeIdentifier(t *testing.T) { } } -// assertNodeEqual 断言两个表达式节点相等(测试辅助函数) +// assertNodeEqual asserts that two expression nodes are equal (test auxiliary function) func assertNodeEqual(t *testing.T, expected, actual *ExprNode) { if expected == nil && actual == nil { return @@ -390,21 +390,21 @@ func assertNodeEqual(t *testing.T, expected, actual *ExprNode) { return } - // 比较节点类型和值 + // Compare node types and values assert.Equal(t, expected.Type, actual.Type, "节点类型应该相等") assert.Equal(t, expected.Value, actual.Value, "节点值应该相等") - // 递归比较左右子节点 + // Recursively compare left and right child nodes assertNodeEqual(t, expected.Left, actual.Left) assertNodeEqual(t, expected.Right, actual.Right) - // 比较函数参数 + // Compare function parameters assert.Equal(t, len(expected.Args), len(actual.Args), "函数参数数量应该相等") for i := range expected.Args { assertNodeEqual(t, expected.Args[i], actual.Args[i]) } - // 比较CASE表达式 + // Compare CASE expressions if expected.CaseExpr == nil && actual.CaseExpr == nil { return } diff --git a/expr/validator_test.go b/expr/validator_test.go index 133fa16..3d73cdd 100644 --- a/expr/validator_test.go +++ b/expr/validator_test.go @@ -7,7 +7,7 @@ import ( "github.com/stretchr/testify/assert" ) -// TestValidateExpressionNode 测试表达式节点验证功能 +// TestValidateExpressionNode Tests expression node validation functionality func TestValidateExpressionNode(t *testing.T) { tests := []struct { name string @@ -69,7 +69,7 @@ func TestValidateExpressionNode(t *testing.T) { }, false, }, - // 错误情况 + // Error case {"空节点", nil, true}, {"无效数字", &ExprNode{Type: TypeNumber, Value: "abc"}, true}, {"无效字段名", &ExprNode{Type: TypeField, Value: "123field"}, true}, @@ -109,14 +109,14 @@ func TestValidateExpressionNode(t *testing.T) { } } -// TestValidateExpression 测试公共表达式验证接口 +// TestValidateExpression tests the common expression verification interface func TestValidateExpression(t *testing.T) { tests := []struct { name string expr string wantErr bool }{ - // 有效表达式 + // Valid expression {"简单数字", "123", false}, {"简单字段", "field1", false}, {"算术表达式", "1 + 2", false}, @@ -128,7 +128,7 @@ func TestValidateExpression(t *testing.T) { {"逻辑表达式", "field1 > 0 AND field2 < 100", false}, {"嵌套函数", "max(abs(field1), abs(field2))", false}, - // 无效表达式 + // Invalid expressions {"空表达式", "", true}, {"只有空格", " ", true}, {"括号不匹配1", "(1 + 2", true}, @@ -158,7 +158,7 @@ func TestValidateExpression(t *testing.T) { } } -// TestValidateNumberNode 测试数字节点验证 +// TestValidateNumberNode Validates the digital node func TestValidateNumberNode(t *testing.T) { tests := []struct { name string @@ -174,7 +174,7 @@ func TestValidateNumberNode(t *testing.T) { {"负科学计数法", "-1.5e-3", false}, {"小数点开头", ".5", false}, {"小数点结尾", "5.", false}, - // 错误情况 + // Error case {"空字符串", "", true}, {"字母", "abc", true}, {"多个小数点", "3.14.15", true}, @@ -197,7 +197,7 @@ func TestValidateNumberNode(t *testing.T) { } } -// TestValidateStringNode 测试字符串节点验证 +// TestValidateStringNode Tests string node validation func TestValidateStringNode(t *testing.T) { tests := []struct { name string @@ -211,7 +211,7 @@ func TestValidateStringNode(t *testing.T) { {"包含转义的字符串", "'hello\\world'", false}, {"包含单引号的双引号字符串", "\"hello'world\"", false}, {"包含双引号的单引号字符串", "'hello\"world'", false}, - // 错误情况 + // Error case {"未闭合单引号", "'hello", true}, {"未闭合双引号", "\"hello", true}, {"没有引号", "hello", true}, @@ -234,7 +234,7 @@ func TestValidateStringNode(t *testing.T) { } } -// TestValidateFieldNode 测试字段节点验证 +// TestValidateFieldNode Verification of the test field node func TestValidateFieldNode(t *testing.T) { tests := []struct { name string @@ -250,7 +250,7 @@ func TestValidateFieldNode(t *testing.T) { {"反引号字段", "`field name`", false}, {"反引号包含特殊字符", "`user.name`", false}, {"反引号包含空格", "`user name`", false}, - // 错误情况 + // Error case {"空字段名", "", true}, {"数字开头", "123field", true}, {"包含特殊字符", "field-name", true}, @@ -274,7 +274,7 @@ func TestValidateFieldNode(t *testing.T) { } } -// TestValidateOperatorNode 测试运算符节点验证 +// TestValidateOperatorNode Node verifies the test operator node func TestValidateOperatorNode(t *testing.T) { tests := []struct { name string @@ -326,7 +326,7 @@ func TestValidateOperatorNode(t *testing.T) { nil, false, }, - // 错误情况 + // Error case {"无效运算符", "@", &ExprNode{Type: TypeNumber, Value: "1"}, &ExprNode{Type: TypeNumber, Value: "2"}, true}, {"缺少左操作数", "+", nil, &ExprNode{Type: TypeNumber, Value: "2"}, true}, {"缺少右操作数(双操作数运算符)", "+", &ExprNode{Type: TypeNumber, Value: "1"}, nil, true}, @@ -353,9 +353,9 @@ func TestValidateOperatorNode(t *testing.T) { } } -// TestValidateFunctionNode 测试函数节点验证 +// TestValidateFunctionNode Validates the test function node func TestValidateFunctionNode(t *testing.T) { - // 测试函数Value为空的情况 + // Test when the function Value is empty t.Run("函数名为空", func(t *testing.T) { node := &ExprNode{ Type: TypeFunction, @@ -404,7 +404,7 @@ func TestValidateFunctionNode(t *testing.T) { []*ExprNode{}, false, }, - // 错误情况 + // Error case {"未知函数", "unknown", []*ExprNode{{Type: TypeNumber, Value: "1"}}, true}, {"ABS参数数量错误", "abs", []*ExprNode{}, true}, {"POW参数数量错误", "pow", []*ExprNode{{Type: TypeNumber, Value: "2"}}, true}, @@ -429,7 +429,7 @@ func TestValidateFunctionNode(t *testing.T) { } } -// TestValidateFunctionArgs 测试函数参数验证 +// TestValidateFunctionArgs Validates the function parameters func TestValidateFunctionArgs(t *testing.T) { tests := []struct { name string @@ -437,38 +437,38 @@ func TestValidateFunctionArgs(t *testing.T) { args []*ExprNode wantErr bool }{ - // 单参数函数 + // Single-parameter function {"ABS正确参数", "abs", []*ExprNode{{Type: TypeNumber, Value: "1"}}, false}, {"ABS参数过少", "abs", []*ExprNode{}, true}, {"ABS参数过多", "abs", []*ExprNode{{Type: TypeNumber, Value: "1"}, {Type: TypeNumber, Value: "2"}}, true}, - // 双参数函数 + // Two-parameter function {"POW正确参数", "pow", []*ExprNode{{Type: TypeNumber, Value: "2"}, {Type: TypeNumber, Value: "3"}}, false}, {"POW参数过少", "pow", []*ExprNode{{Type: TypeNumber, Value: "2"}}, true}, {"POW参数过多", "pow", []*ExprNode{{Type: TypeNumber, Value: "2"}, {Type: TypeNumber, Value: "3"}, {Type: TypeNumber, Value: "4"}}, true}, - // 可变参数函数 + // Variable parameter function {"MAX单参数", "max", []*ExprNode{{Type: TypeNumber, Value: "1"}}, false}, {"MAX多参数", "max", []*ExprNode{{Type: TypeNumber, Value: "1"}, {Type: TypeNumber, Value: "2"}, {Type: TypeNumber, Value: "3"}}, false}, {"MAX无参数", "max", []*ExprNode{}, true}, - // 无参数函数(如果有的话) + // Parameterless functions (if available) {"COUNT无参数", "count", []*ExprNode{}, false}, - {"COUNT有参数", "count", []*ExprNode{{Type: TypeNumber, Value: "1"}}, false}, // COUNT可以有参数 + {"COUNT有参数", "count", []*ExprNode{{Type: TypeNumber, Value: "1"}}, false}, // COUNT can have parameters - // 未知函数 + // Unknown function {"未知函数", "unknown", []*ExprNode{{Type: TypeNumber, Value: "1"}}, true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - // 创建一个函数节点来测试 + // Create a function node to test node := &ExprNode{ Type: TypeFunction, Value: tt.funcName, Args: tt.args, } - // 使用validateFunctionNode来验证函数名和参数数量 + // Use validateFunctionNode to verify function names and number of parameters err := validateFunctionNode(node) if tt.wantErr { assert.Error(t, err, "应该返回错误") @@ -479,7 +479,7 @@ func TestValidateFunctionArgs(t *testing.T) { } } -// TestValidateCaseNode 测试CASE节点验证 +// TestValidateCaseNode Tests CASE node verification func TestValidateCaseNode(t *testing.T) { tests := []struct { name string @@ -511,11 +511,11 @@ func TestValidateCaseNode(t *testing.T) { WhenClauses: []WhenClause{ { Condition: &ExprNode{Type: TypeString, Value: "'active'"}, - Result: &ExprNode{Type: TypeNumber, Value: "1"}, + Result: &ExprNode{Type: TypeNumber, Value: "1"}, }, { Condition: &ExprNode{Type: TypeString, Value: "'inactive'"}, - Result: &ExprNode{Type: TypeNumber, Value: "0"}, + Result: &ExprNode{Type: TypeNumber, Value: "0"}, }, }, ElseResult: &ExprNode{Type: TypeNumber, Value: "-1"}, @@ -567,7 +567,7 @@ func TestValidateCaseNode(t *testing.T) { }, false, }, - // 错误情况 + // Error case {"没有WHEN子句", &CaseExpression{WhenClauses: []WhenClause{}, ElseResult: &ExprNode{Type: TypeNumber, Value: "0"}}, true}, {"WHEN条件为空", &CaseExpression{ WhenClauses: []WhenClause{ @@ -603,7 +603,7 @@ func TestValidateCaseNode(t *testing.T) { }, true}, } - // 添加CaseExpr为nil的测试用例 + // Add a test case where CaseExpr is nil t.Run("CaseExpr为nil", func(t *testing.T) { node := &ExprNode{Type: TypeCase, CaseExpr: nil} err := validateCaseNode(node) @@ -624,14 +624,14 @@ func TestValidateCaseNode(t *testing.T) { } } -// TestIsValidFieldName 测试字段名验证 +// TestIsValidFieldName to validate the test field name func TestIsValidFieldName(t *testing.T) { tests := []struct { name string fieldName string expected bool }{ - // 有效情况 + // Effective situation {"简单字段名", "field", true}, {"下划线开头", "_field", true}, {"包含数字", "field123", true}, @@ -646,7 +646,7 @@ func TestIsValidFieldName(t *testing.T) { {"反引号包含连字符", "`field-name`", true}, {"反引号包含各种符号", "`field@#$%^&*()`", true}, - // 无效情况 + // Invalid cases {"空字段名", "", false}, {"数字开头", "123field", false}, {"包含连字符", "field-name", false}, @@ -672,7 +672,7 @@ func TestIsValidFieldName(t *testing.T) { } } -// TestValidateTokens 测试标记列表验证 +// TestValidateTokens test tag list verification func TestValidateTokens(t *testing.T) { tests := []struct { name string @@ -682,7 +682,7 @@ func TestValidateTokens(t *testing.T) { {"有效标记列表", []string{"a", "+", "b"}, false}, {"有效函数调用", []string{"abs", "(", "x", ")"}, false}, {"有效CASE表达式", []string{"CASE", "WHEN", "a", ">", "0", "THEN", "1", "ELSE", "0", "END"}, false}, - // 错误情况 + // Error case {"空标记列表", []string{}, true}, {"括号不匹配", []string{"(", "a", "+", "b"}, true}, {"连续运算符", []string{"a", "+", "+", "b"}, true}, @@ -702,7 +702,7 @@ func TestValidateTokens(t *testing.T) { } } -// TestValidateParentheses 测试括号验证 +// TestValidateParentheses test parentheses verification func TestValidateParentheses(t *testing.T) { tests := []struct { name string @@ -713,7 +713,7 @@ func TestValidateParentheses(t *testing.T) { {"嵌套括号", []string{"(", "(", "a", "+", "b", ")", "*", "c", ")"}, false}, {"函数括号", []string{"abs", "(", "x", ")"}, false}, {"无括号", []string{"a", "+", "b"}, false}, - // 错误情况 + // Error case {"缺少右括号", []string{"(", "a", "+", "b"}, true}, {"缺少左括号", []string{"a", "+", "b", ")"}, true}, {"括号顺序错误", []string{")", "a", "+", "b", "("}, true}, @@ -732,7 +732,7 @@ func TestValidateParentheses(t *testing.T) { } } -// TestValidateTokenOrder 测试标记顺序验证 +// TestValidateTokenOrder verifies the order of test tags func TestValidateTokenOrder(t *testing.T) { tests := []struct { name string @@ -742,7 +742,7 @@ func TestValidateTokenOrder(t *testing.T) { {"正确顺序", []string{"a", "+", "b"}, false}, {"函数调用", []string{"abs", "(", "x", ")"}, false}, {"复杂表达式", []string{"a", "+", "b", "*", "c"}, false}, - // 错误情况 + // Error case {"连续运算符", []string{"a", "+", "+", "b"}, true}, {"运算符开头", []string{"+", "a"}, true}, {"运算符结尾", []string{"a", "+"}, true}, @@ -768,13 +768,13 @@ func TestValidateTokenOrder(t *testing.T) { } } -// TestValidateSyntax 测试语法验证 +// TestValidateSyntax tests syntax verification func TestValidateParenthesisNode(t *testing.T) { tests := []struct { - name string - node *ExprNode - wantErr bool - errMsg string + name string + node *ExprNode + wantErr bool + errMsg string }{ { name: "有效的括号表达式", @@ -802,7 +802,7 @@ func TestValidateParenthesisNode(t *testing.T) { Type: TypeParenthesis, Left: &ExprNode{ Type: TypeField, - Value: "", // 空字段名 + Value: "", // Empty field name }, }, wantErr: true, @@ -834,7 +834,7 @@ func TestValidateSyntax(t *testing.T) { expr string wantErr bool }{ - // 有效表达式 + // Valid expression {"有效算术表达式", "a + b", false}, {"有效函数调用", "abs(x)", false}, {"有效CASE表达式", "CASE WHEN a > 0 THEN 1 END", false}, @@ -844,7 +844,7 @@ func TestValidateSyntax(t *testing.T) { {"有效小于等于表达式", "a <= b", false}, {"复杂表达式", "a + b * c", false}, - // 错误情况 + // Error case {"空表达式", "", true}, {"只有空格", " ", true}, {"空括号", "()", true}, diff --git a/functions/README.md b/functions/README.md index 43e0bf7..f85df17 100644 --- a/functions/README.md +++ b/functions/README.md @@ -1,55 +1,55 @@ -# StreamSQL Functions 模块扩展 - -## 概述 - -本次扩展实现了统一的聚合函数和分析函数管理,简化了自定义函数的扩展过程。现在只需要在 `functions` 模块中实现函数,就可以自动在 `aggregator` 模块中使用。 - -## 主要改进 - -### 1. 统一的函数接口 - -- **AggregatorFunction**: 支持增量计算的聚合函数接口 -- **AnalyticalFunction**: 支持状态管理的分析函数接口 -- **Function**: 基础函数接口 - -### 2. 自动适配器 - -- **AggregatorAdapter**: 将 functions 模块的聚合函数适配到 aggregator 模块 -- **AnalyticalAdapter**: 将 functions 模块的分析函数适配到 aggregator 模块 - -### 3. 简化的扩展流程 - -现在添加自定义函数只需要: -1. 在 functions 模块中实现函数 -2. 注册函数和适配器 -3. 无需修改 aggregator 模块 - -## 使用方法 - -### 创建自定义聚合函数 - -```go -// 1. 定义函数结构 -type CustomSumFunction struct { - *BaseFunction - sum float64 -} - -// 2. 实现基础接口 -func (f *CustomSumFunction) Execute(ctx *FunctionContext, args []interface{}) (interface{}, error) { - // 实现函数逻辑 -} - -// 3. 实现AggregatorFunction接口 -func (f *CustomSumFunction) New() AggregatorFunction { - return &CustomSumFunction{BaseFunction: f.BaseFunction} -} - -func (f *CustomSumFunction) Add(value interface{}) { - // 增量计算逻辑 -} - -func (f *CustomSumFunction) Result() interface{} { +# StreamSQL Functions Module expansion + +## Overview + +This extension implements unified management of aggregate and analysis functions, simplifying the extension process for custom functions. Now, you only need to implement the function in the `functions` module to automatically use it in the `aggregator` module. + +## Major improvements + +### 1. Unified function interface + +- **AggregatorFunction**: Aggregate function interface supporting incremental computation +- **AnalyticalFunction**: Interface for analysis functions supporting state management +- **Function**: Basic function interface + +### 2. Automatic adapter + +- **AggregatorAdapter**: Adapt the aggregation function of the functions module to the aggregator module +- **AnalyticalAdapter**: Adapt the analysis functions of the functions module to the aggregator module + +### 3. Simplified expansion process + +Now, to add a custom function, you only need: +1. Implement functions in the functions module +2. Register functions and adapters +3. No need to modify aggregator modules + +## How to use + +### Create a custom aggregate function + +```go +// 1. Define the function structure +type CustomSumFunction struct { + *BaseFunction + sum float64 +} + +// 2. Implement basic interfaces +func (f *CustomSumFunction) Execute(ctx *FunctionContext, args []interface{}) (interface{}, error) { + // Implement functional logic +} + +// 3. Implement AggregatorFunction interfaces +func (f *CustomSumFunction) New() AggregatorFunction { + return &CustomSumFunction{BaseFunction: f.BaseFunction} +} + +func (f *CustomSumFunction) Add(value interface{}) { + // Incremental calculation logic +} + +func (f *CustomSumFunction) Result() interface{} { return f.sum } @@ -58,95 +58,95 @@ func (f *CustomSumFunction) Reset() { } func (f *CustomSumFunction) Clone() AggregatorFunction { - return &CustomSumFunction{BaseFunction: f.BaseFunction, sum: f.sum} -} - -// 4. 注册函数(TypeAggregation 自动接通聚合适配器,无需手动 RegisterAggregatorAdapter) -func init() { - Register(NewCustomSumFunction()) -} -``` - -### 创建自定义分析函数 - -```go -// 1. 定义函数结构(分析函数本身无可变状态,跨行状态放在下面的 State 里) -type CustomAnalyticalFunction struct { - *BaseFunction -} - -// 2. 实现基础接口(Execute 标量路径禁用:分析函数需跨行状态,由状态机求值) -func (f *CustomAnalyticalFunction) Execute(ctx *FunctionContext, args []any) (any, error) { - return nil, fmt.Errorf("analytic function %q must be used as a field or with OVER", f.GetName()) -} - -// 3. 实现 StatefulAnalytic:NewState 返回一份独立状态,引擎为每个 PARTITION 各持一份 -func (f *CustomAnalyticalFunction) NewState() AnalyticState { - return &customAnalyticalState{} -} + return &CustomSumFunction{BaseFunction: f.BaseFunction, sum: f.sum} +} + +// 4. Register Function (TypeAggregation Automatically connect the aggregation adapter, no manual RegisterAggregatorAdapter required) +func init() { + Register(NewCustomSumFunction()) +} +``` + +### Create a custom analysis function + +```go +// 1. Define the function structure (analyze that the function itself has no mutable state; the interline state is placed in the State below) +type CustomAnalyticalFunction struct { + *BaseFunction +} + +// 2. Implement basic interfaces (Execute Scalar path disabled: analysis functions must cross lines and be evaluated by state machines) +func (f *CustomAnalyticalFunction) Execute(ctx *FunctionContext, args []any) (any, error) { + return nil, fmt.Errorf("analytic function %q must be used as a field or with OVER", f.GetName()) +} + +// 3. Implement StatefulAnalytic: NewState returns a single independent state, with the engine holding one copy for each PARTITION +func (f *CustomAnalyticalFunction) NewState() AnalyticState { + return &customAnalyticalState{} +} type customAnalyticalState struct { - prev any -} - -// Apply 每条事件调用:用当前行参数更新状态,返回当前结果(这里返回上一行的值,即 lag 语义) -func (s *customAnalyticalState) Apply(args []any) any { - cur := args[0] - result := s.prev + prev any +} + +// Apply Each event call: Update the status with the current line parameter and return the current result (here returns the value of the previous line, i.e., lag semantics) +func (s *customAnalyticalState) Apply(args []any) any { + cur := args[0] + result := s.prev s.prev = cur return result } - -func (s *customAnalyticalState) Reset() { s.prev = nil } - -// 4. 注册函数(分析函数 Register 即可,无需 RegisterAnalyticalAdapter) -func init() { - Register(NewCustomAnalyticalFunction()) -} -``` - -### 使用简化的注册方式 - -```go -// 注册简单的自定义函数 -RegisterCustomFunction("double", TypeAggregation, "数学函数", "将值乘以2", 1, 1, - func(ctx *FunctionContext, args []interface{}) (interface{}, error) { - val, err := cast.ToFloat64E(args[0]) + +func (s *customAnalyticalState) Reset() { s.prev = nil } + +// 4. Register functions (analyze function Register, no need to RegisterAnalyticalAdapter) +func init() { + Register(NewCustomAnalyticalFunction()) +} +``` + +### Use a simplified registration method + +```go +// Register simple custom functions +RegisterCustomFunction("double", TypeAggregation, "数学函数", "将值乘以2", 1, 1, + func(ctx *FunctionContext, args []interface{}) (interface{}, error) { + val, err := cast.ToFloat64E(args[0]) if err != nil { return nil, err } return val * 2, nil - }) -``` - -## 内置函数 - -### 聚合函数 -- `sum`: 求和 -- `avg`: 平均值 -- `min`: 最小值 -- `max`: 最大值 -- `count`: 计数 -- `stddev`: 标准差 -- `median`: 中位数 -- `percentile`: 百分位数 -- `collect`: 收集所有值 -- `last_value`: 最后一个值 -- `merge_agg`: 合并聚合 -- `stddevs`: 样本标准差 -- `deduplicate`: 去重 -- `var`: 总体方差 -- `vars`: 样本方差 - -### 分析函数 -- `lag`: 滞后函数 -- `latest`: 最新值 -- `changed_col`: 变化列 -- `had_changed`: 是否变化 - -## 自定义函数示例 - -参考 `custom_example.go` 文件中的示例: -- `CustomProductFunction`: 乘积聚合函数 -- `CustomGeometricMeanFunction`: 几何平均聚合函数 -- `CustomMovingAverageFunction`: 移动平均分析函数 + }) +``` + +## Built-in functions + +### Aggregate Function +- `sum`: Seek harmony +- `avg`: Average +- `min`: Minimum value +- `max`: Maximum value +- `count`: Count +- `stddev`: Standard deviation +- `median`: Median +- `percentile`: percentile +- `collect`: Collect all values +- `last_value`: The last value +- `merge_agg`: Merge and aggregate +- `stddevs`: Standard deviation of the sample +- `deduplicate`: Reduce deduplication +- `var`: Population variance +- `vars`: Sample variance + +### Analyze the function +- `lag`: The lag function +- `latest`: Latest value +- `changed_col`: Change column +- `had_changed`: Whether it changes + +## Custom function example + +Refer to the example from the `custom_example.go` file: +- `CustomProductFunction`: Product aggregation function +- `CustomGeometricMeanFunction`: Geometric mean aggregation function +- `CustomMovingAverageFunction`: Moving average analysis function diff --git a/functions/aggregator_adapter.go b/functions/aggregator_adapter.go index c38c35c..4fb2fcc 100644 --- a/functions/aggregator_adapter.go +++ b/functions/aggregator_adapter.go @@ -52,9 +52,9 @@ var ( adapterMutex sync.RWMutex ) -// RegisterAggregatorAdapter 注册聚合器适配器。通常无需手动调用: -// functions.Register 注册 TypeAggregation 函数时会自动调用本函数(见 registry.go)。 -// 仅在需要为已注册函数单独补建适配器时才显式调用。 +// RegisterAggregatorAdapter Registers the aggregator adapter. Usually, manual calls are not required: +// functions.Register automatically calls this function when registering the TypeAggregation function (see registry.go). +// It is only explicitly called when a separate adapter needs to be added to a registered function. func RegisterAggregatorAdapter(name string) error { adapterMutex.Lock() defer adapterMutex.Unlock() diff --git a/functions/aggregator_adapter_test.go b/functions/aggregator_adapter_test.go index a77502a..470ddcd 100644 --- a/functions/aggregator_adapter_test.go +++ b/functions/aggregator_adapter_test.go @@ -7,90 +7,90 @@ import ( "github.com/stretchr/testify/require" ) -// TestAggregatorAdapterBasic 测试聚合器适配器基本功能 +// TestAggregatorAdapterBasic Tests the basic functions of the aggregator adapter func TestAggregatorAdapterBasic(t *testing.T) { - // 测试创建聚合器适配器 + // Test the creation of aggregator adapters adapter, err := NewAggregatorAdapter("sum") require.NoError(t, err) require.NotNil(t, adapter) - // 测试GetFunctionName + // Test GetFunctionName funcName := adapter.GetFunctionName() assert.Equal(t, "sum", funcName) - // 测试New方法 + // Test the new method newAdapter := adapter.New() assert.NotNil(t, newAdapter) - // 测试Add和Result方法 + // Test the Add and Result methods adapter.Add(10) adapter.Add(20) adapter.Add(30) result := adapter.Result() assert.Equal(t, 60.0, result) - // 测试创建不存在的聚合器 + // Test to create aggregators that don't exist _, err = NewAggregatorAdapter("nonexistent") assert.Error(t, err) } -// TestAggregatorAdapterWithNilFunction 测试聚合器适配器的nil函数情况 +// TestAggregatorAdapterWithNilFunction tests the nil function of the aggregator adapter func TestAggregatorAdapterWithNilFunction(t *testing.T) { adapter := &AggregatorAdapter{aggFunc: nil} funcName := adapter.GetFunctionName() assert.Equal(t, "", funcName) } -// TestRegisterAggregatorAdapter 测试注册聚合器适配器 +// TestRegisterAggregatorAdapter Tests the registration aggregator adapter func TestRegisterAggregatorAdapter(t *testing.T) { - // 注册聚合器适配器 + // Register the aggregator adapter err := RegisterAggregatorAdapter("sum") assert.NoError(t, err) - // 获取聚合器适配器 + // Get the aggregator adapter constructor, exists := GetAggregatorAdapter("sum") assert.True(t, exists) assert.NotNil(t, constructor) - // 使用构造函数创建实例 + // Create instances using constructors instance := constructor() assert.NotNil(t, instance) - // 测试不存在的适配器 + // Testing adapters that don't exist _, exists = GetAggregatorAdapter("nonexistent") assert.False(t, exists) } -// TestCreateBuiltinAggregatorFromFunctions 测试从函数模块创建内置聚合器 +// TestCreateBuiltinAggregatorFromFunctions tests the built-in aggregator created from function modules func TestCreateBuiltinAggregatorFromFunctions(t *testing.T) { - // 先注册一个适配器 + // First, register an adapter err := RegisterAggregatorAdapter("sum") assert.NoError(t, err) - // 从注册的适配器创建 + // Created from the registered adapter aggregator := CreateBuiltinAggregatorFromFunctions("sum") assert.NotNil(t, aggregator) - // 从未注册的函数直接创建 + // Directly created from unregistered functions aggregator2 := CreateBuiltinAggregatorFromFunctions("avg") assert.NotNil(t, aggregator2) - // 测试不存在的聚合器 + // Testing aggregators that don't exist aggregator3 := CreateBuiltinAggregatorFromFunctions("nonexistent") assert.Nil(t, aggregator3) } -// TestAggregatorAdapterErrorHandling 测试聚合器适配器错误处理 +// TestAggregatorAdapterErrorHandling Tests aggregator adapter error handling func TestAggregatorAdapterErrorHandling(t *testing.T) { - // 注册一个会失败的适配器(使用不存在的函数名) + // Register an adapter that will fail (using a non-existent function name) err := RegisterAggregatorAdapter("invalid_function") assert.NoError(t, err) - // 获取并尝试创建实例 + // Obtain and attempt to create instances constructor, exists := GetAggregatorAdapter("invalid_function") assert.True(t, exists) - // 创建实例应该返回nil(因为函数不存在) + // Creating an instance should return nil (because the function does not exist) instance := constructor() assert.Nil(t, instance) } diff --git a/functions/aggregator_interface_test.go b/functions/aggregator_interface_test.go index 6376d28..1626a79 100644 --- a/functions/aggregator_interface_test.go +++ b/functions/aggregator_interface_test.go @@ -8,7 +8,7 @@ import ( "github.com/stretchr/testify/require" ) -// mockParameterizedAggregator 实现ParameterizedFunction接口用于测试 +// mockParameterizedAggregator implements the ParameterizedFunction interface for testing type mockParameterizedAggregator struct { name string args []any @@ -17,7 +17,7 @@ type mockParameterizedAggregator struct { result any } -// 实现Function接口 +// Implement the Function interface func (m *mockParameterizedAggregator) GetName() string { return m.name } @@ -54,7 +54,7 @@ func (m *mockParameterizedAggregator) GetDescription() string { return "Mock parameterized aggregator" } -// 实现AggregatorFunction接口 +// Implement the AggregatorFunction interface func (m *mockParameterizedAggregator) New() AggregatorFunction { return &mockParameterizedAggregator{ name: m.name, @@ -70,7 +70,7 @@ func (m *mockParameterizedAggregator) Result() any { if m.result != nil { return m.result } - // 默认返回值的数量 + // The default number of return values return len(m.addedValues) } @@ -89,26 +89,26 @@ func (m *mockParameterizedAggregator) Clone() AggregatorFunction { } } -// 实现ParameterizedFunction接口 +// Implement the ParameterizedFunction interface func (m *mockParameterizedAggregator) Init(args []any) error { m.args = args m.initialized = true - // 根据参数设置结果 + // Set the results according to the parameters if len(args) > 0 { if val, ok := args[0].(int); ok { - m.result = val * 10 // 简单的计算逻辑 + m.result = val * 10 // Simple calculation logic } } return nil } -// mockSimpleAggregator 实现AggregatorFunction接口但不实现ParameterizedFunction +// mockSimpleAggregator implements the AggregatorFunction interface but does not implement ParameterizedFunction type mockSimpleAggregator struct { name string values []any } -// 实现Function接口 +// Implement the Function interface func (m *mockSimpleAggregator) GetName() string { return m.name } @@ -145,7 +145,7 @@ func (m *mockSimpleAggregator) GetDescription() string { return "Mock simple aggregator" } -// 实现AggregatorFunction接口 +// Implement the AggregatorFunction interface func (m *mockSimpleAggregator) New() AggregatorFunction { return &mockSimpleAggregator{ name: m.name, @@ -172,54 +172,54 @@ func (m *mockSimpleAggregator) Clone() AggregatorFunction { } } -// TestCreateParameterizedAggregator 测试CreateParameterizedAggregator函数的完整功能 +// TestCreateParameterizedAggregator Test the full functionality of the CreateParameterizedAggregator function func TestCreateParameterizedAggregator(t *testing.T) { t.Run("测试参数化聚合器创建和初始化", func(t *testing.T) { - // 注册测试用的参数化聚合器 + // Parametric aggregators for registration testing mockParamAgg := &mockParameterizedAggregator{name: "test_param_agg"} err := Register(mockParamAgg) require.NoError(t, err) - // 测试创建参数化聚合器 + // Test the creation of parametric aggregators args := []any{5, "test", 3.14} aggregator, err := CreateParameterizedAggregator("test_param_agg", args) require.NoError(t, err) require.NotNil(t, aggregator) - // 验证聚合器类型 + // Verify aggregator types paramAgg, ok := aggregator.(*mockParameterizedAggregator) require.True(t, ok) assert.True(t, paramAgg.initialized) assert.Equal(t, args, paramAgg.args) assert.Equal(t, 50, paramAgg.result) // 5 * 10 = 50 - // 清理 + // Cleanup Unregister("test_param_agg") }) t.Run("测试非参数化聚合器的回退处理", func(t *testing.T) { - // 注册测试用的简单聚合器 + // A simple aggregator for registration testing mockSimpleAgg := &mockSimpleAggregator{name: "test_simple_agg"} err := Register(mockSimpleAgg) require.NoError(t, err) - // 测试创建非参数化聚合器(应该回退到常规创建) + // Test creating a non-parametric aggregator (should rollback to regular creation) args := []any{1, 2, 3} aggregator, err := CreateParameterizedAggregator("test_simple_agg", args) require.NoError(t, err) require.NotNil(t, aggregator) - // 验证聚合器类型 + // Verify aggregator types simpleAgg, ok := aggregator.(*mockSimpleAggregator) require.True(t, ok) assert.Equal(t, "test_simple_agg", simpleAgg.name) - // 清理 + // Cleanup Unregister("test_simple_agg") }) t.Run("测试不存在的聚合器函数", func(t *testing.T) { - // 测试创建不存在的聚合器 + // Test to create aggregators that don't exist args := []any{1, 2, 3} aggregator, err := CreateParameterizedAggregator("non_existent_agg", args) assert.Error(t, err) @@ -228,79 +228,79 @@ func TestCreateParameterizedAggregator(t *testing.T) { }) t.Run("测试参数化聚合器初始化失败", func(t *testing.T) { - // 创建一个会初始化失败的参数化聚合器 + // Create a parameterized aggregator that will initialize failure failingAgg := &mockParameterizedAggregatorWithFailingInit{ mockParameterizedAggregator: mockParameterizedAggregator{name: "failing_agg"}, } err := Register(failingAgg) require.NoError(t, err) - // 测试创建时初始化失败 + // Initialization fails when the test is created args := []any{"invalid"} aggregator, err := CreateParameterizedAggregator("failing_agg", args) assert.Error(t, err) assert.Nil(t, aggregator) assert.Contains(t, err.Error(), "failed to initialize parameterized function") - // 清理 + // Cleanup Unregister("failing_agg") }) t.Run("测试空参数列表", func(t *testing.T) { - // 注册测试用的参数化聚合器 + // Parametric aggregators for registration testing mockParamAgg := &mockParameterizedAggregator{name: "test_empty_args"} err := Register(mockParamAgg) require.NoError(t, err) - // 测试空参数列表 + // Test null parameter list args := []any{} aggregator, err := CreateParameterizedAggregator("test_empty_args", args) require.NoError(t, err) require.NotNil(t, aggregator) - // 验证聚合器已初始化 + // Verify that the aggregator has been initialized paramAgg, ok := aggregator.(*mockParameterizedAggregator) require.True(t, ok) assert.True(t, paramAgg.initialized) assert.Equal(t, args, paramAgg.args) - // 清理 + // Cleanup Unregister("test_empty_args") }) t.Run("测试聚合器功能", func(t *testing.T) { - // 注册测试用的参数化聚合器 + // Parametric aggregators for registration testing mockParamAgg := &mockParameterizedAggregator{name: "test_functionality"} err := Register(mockParamAgg) require.NoError(t, err) - // 创建聚合器 + // Create aggregators args := []any{3} aggregator, err := CreateParameterizedAggregator("test_functionality", args) require.NoError(t, err) require.NotNil(t, aggregator) - // 测试聚合器功能 + // Test the aggregator function aggregator.Add(10) aggregator.Add(20) aggregator.Add(30) - // 验证结果(应该是初始化时设置的值:3 * 10 = 30) + // Verification result (should be the value set at initialization: 3 * 10 = 30) result := aggregator.Result() assert.Equal(t, 30, result) - // 清理 + // Cleanup Unregister("test_functionality") }) } -// mockParameterizedAggregatorWithFailingInit 用于测试初始化失败的情况 +// mockParameterizedAggregatorWithFailingInit is used to test for initialization failures type mockParameterizedAggregatorWithFailingInit struct { mockParameterizedAggregator } func (m *mockParameterizedAggregatorWithFailingInit) Init(args []any) error { - return errors.New("initialization failed") // 返回一个错误 + return errors.New("initialization failed") // Returns an error } func (m *mockParameterizedAggregatorWithFailingInit) New() AggregatorFunction { @@ -313,21 +313,21 @@ func (m *mockParameterizedAggregatorWithFailingInit) New() AggregatorFunction { } } -// TestCreateAggregatorInterface 测试CreateAggregator函数 +// TestCreateAggregatorInterface tests the CreateAggregator function func TestCreateAggregatorInterface(t *testing.T) { t.Run("测试创建存在的聚合器", func(t *testing.T) { - // 注册测试聚合器 + // Register for the test aggregator mockAgg := &mockSimpleAggregator{name: "test_create_agg"} err := Register(mockAgg) require.NoError(t, err) - // 创建聚合器 + // Create aggregators aggregator, err := CreateAggregator("test_create_agg") require.NoError(t, err) require.NotNil(t, aggregator) assert.IsType(t, &mockSimpleAggregator{}, aggregator) - // 清理 + // Cleanup Unregister("test_create_agg") }) @@ -339,19 +339,19 @@ func TestCreateAggregatorInterface(t *testing.T) { }) } -// TestIsAggregatorFunction 测试IsAggregatorFunction函数 +// TestIsAggregatorFunction Test the IsAggregatorFunction function func TestIsAggregatorFunction(t *testing.T) { t.Run("测试已注册的聚合器函数", func(t *testing.T) { - // 注册测试聚合器 + // Register for the test aggregator mockAgg := &mockSimpleAggregator{name: "test_is_agg"} err := Register(mockAgg) require.NoError(t, err) - // 测试IsAggregatorFunction + // Test IsAggregatorFunction isAgg := IsAggregatorFunction("test_is_agg") assert.True(t, isAgg) - // 清理 + // Cleanup Unregister("test_is_agg") }) diff --git a/functions/aggregator_types_test.go b/functions/aggregator_types_test.go index 88b994c..9274cac 100644 --- a/functions/aggregator_types_test.go +++ b/functions/aggregator_types_test.go @@ -4,7 +4,7 @@ import ( "testing" ) -// TestAggregateTypeConstants 测试聚合类型常量 +// TestAggregateTypeConstants Tests the aggregation type constants func TestAggregateTypeConstants(t *testing.T) { tests := []struct { name string @@ -44,7 +44,7 @@ func TestAggregateTypeConstants(t *testing.T) { } } -// TestStringConstants 测试字符串常量 +// TestStringConstants tests string constants func TestStringConstants(t *testing.T) { tests := []struct { name string @@ -84,17 +84,17 @@ func TestStringConstants(t *testing.T) { } } -// TestRegisterLegacyAggregator 测试注册遗留聚合器 +// TestRegisterLegacyAggregator tests the Legacy aggregator func TestRegisterLegacyAggregator(t *testing.T) { - // 创建一个测试聚合器构造函数 + // Create a test aggregator constructor constructor := func() LegacyAggregatorFunction { return &TestLegacyAggregator{} } - // 注册聚合器 + // Register aggregators RegisterLegacyAggregator("test_agg", constructor) - // 验证注册成功 + // Verify registration successfully legacyRegistryMutex.RLock() _, exists := legacyAggregatorRegistry["test_agg"] legacyRegistryMutex.RUnlock() @@ -103,13 +103,13 @@ func TestRegisterLegacyAggregator(t *testing.T) { t.Error("Failed to register legacy aggregator") } - // 测试创建聚合器 + // Test the creation of aggregators createdAgg := CreateLegacyAggregator("test_agg") if createdAgg == nil { t.Error("Failed to create legacy aggregator") } - // 测试聚合器功能 + // Test the aggregator function createdAgg.Add(10) createdAgg.Add(20) result := createdAgg.Result() @@ -145,126 +145,125 @@ func TestCreateLegacyAggregatorPercentile(t *testing.T) { } } -// TestFunctionAggregatorWrapper 测试函数聚合器包装器 +// TestFunctionAggregatorWrapper is a test function aggregator wrapper func TestFunctionAggregatorWrapper(t *testing.T) { - // 创建一个测试聚合器函数 + // Create a test aggregator function testAgg := &TestAggregatorFunction{} - // 创建一个测试适配器 + // Create a test adapter adapter := &AggregatorAdapter{ aggFunc: testAgg, } wrapper := &FunctionAggregatorWrapper{adapter: adapter} - // 测试New方法 + // Test the new method newWrapper := wrapper.New() if newWrapper == nil { t.Error("New() should return a new wrapper") } - // 测试GetContextKey方法 + // Test the GetContextKey method contextKey := wrapper.GetContextKey() if contextKey != "" { t.Logf("Context key: %s", contextKey) } } -// TestLegacyAggregator 测试用的遗留聚合器实现 +// TestLegacyAggregator implementation for testing type TestLegacyAggregator struct { sum int } -// New 创建新的聚合器实例 +// New: Create a new aggregator instance func (t *TestLegacyAggregator) New() LegacyAggregatorFunction { return &TestLegacyAggregator{} } -// Add 添加值 +// Add value func (t *TestLegacyAggregator) Add(value any) { if v, ok := value.(int); ok { t.sum += v } } -// Result 返回聚合结果 +// Result: Returns the aggregated result func (t *TestLegacyAggregator) Result() any { return t.sum } -// TestAggregatorFunction 测试用的聚合器函数实现 +// TestAggregatorFunction is an aggregator function implemented for testing type TestAggregatorFunction struct { sum int } -// New 创建新的聚合器实例 +// New: Create a new aggregator instance func (t *TestAggregatorFunction) New() AggregatorFunction { return &TestAggregatorFunction{} } -// Add 添加值 +// Add value func (t *TestAggregatorFunction) Add(value any) { if v, ok := value.(int); ok { t.sum += v } } -// Result 返回聚合结果 +// Result: Returns the aggregated result func (t *TestAggregatorFunction) Result() any { return t.sum } -// Reset 重置聚合器状态 +// Reset: Resets the aggregator state func (t *TestAggregatorFunction) Reset() { t.sum = 0 } -// Clone 克隆聚合器 +// Clone cloning aggregator func (t *TestAggregatorFunction) Clone() AggregatorFunction { return &TestAggregatorFunction{sum: t.sum} } -// GetName 返回函数名称 +// GetName returns the function name func (t *TestAggregatorFunction) GetName() string { return "test_aggregator" } -// GetType 返回函数类型 +// GetType returns the function type func (t *TestAggregatorFunction) GetType() FunctionType { return TypeAggregation } -// GetCategory 返回函数分类 +// GetCategory returns the function classification func (t *TestAggregatorFunction) GetCategory() string { return "test" } -// GetDescription 返回函数描述 +// GetDescription returns the function description func (t *TestAggregatorFunction) GetDescription() string { return "Test aggregator function" } -// GetAliases 返回函数别名 +// GetAliases returns the function alias func (t *TestAggregatorFunction) GetAliases() []string { return []string{} } -// Validate 验证参数 +// Validate the parameters func (t *TestAggregatorFunction) Validate(args []any) error { return nil } -// Execute 执行函数 +// Execute the function func (t *TestAggregatorFunction) Execute(ctx *FunctionContext, args []any) (any, error) { return t.Result(), nil } -// GetMinArgs 返回最小参数数量 +// GetMinArgs returns the minimum number of parameters func (t *TestAggregatorFunction) GetMinArgs() int { return 1 } -// GetMaxArgs 返回最大参数数量 +// GetMaxArgs returns the maximum number of parameters func (t *TestAggregatorFunction) GetMaxArgs() int { return 1 } - diff --git a/functions/analytic_acc.go b/functions/analytic_acc.go index 5dae021..3d70e25 100644 --- a/functions/analytic_acc.go +++ b/functions/analytic_acc.go @@ -2,19 +2,19 @@ package functions import "fmt" -// accState 是 acc_sum/acc_max/acc_min/acc_count/acc_avg 的通用累积状态。 -// 累积范围为规则生命周期。可选的条件累计:acc_xxx(expr, startExpr, resetExpr), -// startExpr 命中或已开始才累计,resetExpr 命中则归零并停止(直到再次 start)。 +// accState is the universal cumulative state of acc_sum/acc_max/acc_min/acc_count/acc_avg. +// The accumulation range is the regular lifecycle. Optional cumulative conditions: acc_xxx(expr, startExpr, resetExpr), +// startExpr Accumulate only after hits or have started; resetExpr hits reset to zero and stop (until start again). type accState struct { kind string sum float64 count int64 - num float64 // max/min 当前极值 + num float64 // max/min is the current extremum hasNum bool - started bool // 条件累计:是否已进入累计阶段 + started bool // Condition accumulation: Has it entered the accumulation phase? } -// resetState 归零累加器并退出累计阶段(保留 kind)。 +// resetState Zeros the accumulator and exits the accumulation phase (retaining kind). func (s *accState) resetState() { s.sum = 0 s.count = 0 @@ -24,7 +24,7 @@ func (s *accState) resetState() { } func (s *accState) Apply(args []any) any { - // 条件累计:args[1]=开始点,args[2]=重置点(可选布尔表达式)。 + // Conditional cumulation: args[1] = start point, args[2] = reset point (optional Boolean expression). hasStart := len(args) >= 2 hasReset := len(args) >= 3 if hasReset && AnalyticToBool(args[2]) { @@ -34,7 +34,7 @@ func (s *accState) Apply(args []any) any { if hasStart { started := AnalyticToBool(args[1]) if !started && !s.started { - return s.result() // 未进入累计阶段,不计 + return s.result() // If the counting stage is not reached, it is not counted } s.started = true } @@ -56,7 +56,7 @@ func (s *accState) Apply(args []any) any { } s.hasNum = true } else if s.kind == "acc_count" && val != nil { - // acc_count 计数表达式结果(含非数字列)。 + // acc_count Results of counting expressions (including non-numeric columns). s.count++ } } @@ -70,7 +70,7 @@ func (s *accState) result() any { case "acc_count": return s.count case "acc_avg": - // 空累积返回 nil(与 acc_max/min 一致),避免"无数据"被当成"均值 0"。 + // Null cumulative returns nil (consistent with acc_max/min), avoiding "no data" being mistaken for "mean 0". if s.count == 0 { return nil } @@ -91,7 +91,7 @@ func (s *accState) result() any { func (s *accState) Reset() { kind := s.kind; *s = accState{}; s.kind = kind } -// accFunction acc_* 通用函数(TypeAnalytical)。直连路径走 NewState 的 accState。 +// accFunction acc_* General function (TypeAnalytical). Direct connection paths go through NewState's accState. type accFunction struct { *BaseFunction kind string @@ -99,7 +99,7 @@ type accFunction struct { func (f *accFunction) Validate(args []any) error { return f.ValidateArgCount(args) } -// Execute 标量路径禁用:分析函数需跨行状态,只能作为独立字段/OVER 由状态机求值。 +// Execute scalar path disabled: The analysis function needs to be cross-line and can only be used as an independent field/OVER evaluated by the state machine. func (f *accFunction) Execute(ctx *FunctionContext, args []any) (any, error) { return nil, fmt.Errorf("analytic function %q must be used as a field or with OVER, not in a scalar expression", f.GetName()) } @@ -122,7 +122,7 @@ func NewAccAvgFunction() *accFunction { return &accFunction{BaseFunction: NewBaseFunction("acc_avg", TypeAnalytical, "分析函数", "累积平均值", 1, 3), kind: "acc_avg"} } -// changedColState changed_col 状态:变化时返回新值,未变化返回 nil。 +// changedColState changed_col State: Returns a new value when it changes; returns nil when unchanged. type changedColState struct { prev any hasPrev bool @@ -138,13 +138,13 @@ func (s *changedColState) Apply(args []any) any { val = args[1] } if ignoreNull && val == nil { - return nil // null 不触发变化 + return nil // null does not trigger changes } var result any if !s.hasPrev || !analyticEqual(s.prev, val) { - result = val // 发生变化,返回新值 + result = val // Changes occur, returning new values } else { - result = nil // 未变化 + result = nil // Not yet changed } s.prev = val s.hasPrev = true @@ -153,19 +153,19 @@ func (s *changedColState) Apply(args []any) any { func (s *changedColState) Reset() { s.prev = nil; s.hasPrev = false } -// NewState 实现 StatefulAnalytic(changed_col 走直连路径状态机)。 +// NewState implements StatefulAnalytic (changed_col takes a direct path to the state machine). func (f *ChangedColFunction) NewState() AnalyticState { return &changedColState{} } -// changedColsState changed_cols(prefix, ignoreNull, expr...) 多列状态: -// 返回 {prefix+列名: 新值} 仅含发生变化的列。 +// changedColsState changed_cols(prefix, ignoreNull, expr...) Multi-column status: +// Returns {prefix+column_name: new value} contains only the columns that have changed. type changedColsState struct { prev map[string]any } -// Apply 单列接口占位;多列函数由引擎调 ApplyColumns。 +// Apply: Single-column interface spacehold; Multi-column functions are called ApplyColumns by the engine. func (s *changedColsState) Apply(args []any) any { return nil } -// ApplyColumns 比较各列与上次值,返回变化列的 {prefix+列名: 新值}。 +// ApplyColumns compares each column with the previous value and returns {prefix+ column name: new value} for the changing column. func (s *changedColsState) ApplyColumns(prefix string, ignoreNull bool, cols map[string]any) map[string]any { if s.prev == nil { s.prev = make(map[string]any, len(cols)) @@ -186,7 +186,7 @@ func (s *changedColsState) ApplyColumns(prefix string, ignoreNull bool, cols map func (s *changedColsState) Reset() { s.prev = nil } -// ChangedColsFunction changed_cols(prefix, ignoreNull, expr...) 多列变化检测(仅 SELECT)。 +// ChangedColsFunction changed_cols(prefix, ignoreNull, expr...) Multi-column change detection (SELECT only). type ChangedColsFunction struct { *BaseFunction } @@ -197,7 +197,7 @@ func NewChangedColsFunction() *ChangedColsFunction { func (f *ChangedColsFunction) Validate(args []any) error { return f.ValidateArgCount(args) } -// Execute 标量路径禁用:多列分析函数只能作为独立字段由状态机求值。 +// Execute Scalar Path Disabled: Multi-column analysis functions can only be evaluated as independent fields by state machine. func (f *ChangedColsFunction) Execute(ctx *FunctionContext, args []any) (any, error) { return nil, fmt.Errorf("analytic function %q must be used as a field or with OVER, not in a scalar expression", f.GetName()) } diff --git a/functions/analytic_state.go b/functions/analytic_state.go index 221d94c..7366670 100644 --- a/functions/analytic_state.go +++ b/functions/analytic_state.go @@ -5,38 +5,38 @@ import ( "strings" ) -// AnalyticState 分析函数的流级状态机。每条事件调 Apply: -// 用已从当前行解析出的 args 更新状态并返回当前结果值。每个 PARTITION 各持一份 -// 独立状态。与 AggregatorFunction 的批量 Add/Result 不同,这是逐条 Apply。 +// AnalyticState analyzes the flow level state machine of the function. Each event is adjusted to apply: +// Update the status with ARGS parsed from the current row and return the current result value. Each PARTITION holds one copy +// Independent state. Unlike AggregatorFunction's batch Add/Result, this applies one item at a time. type AnalyticState interface { - // Apply 用 args(args[0] 为主参数值,后续为 offset/default/ignoreNull 等) - // 更新状态,返回当前行的分析结果。 + // Apply: Use args (args[0] as the main parameter, followed by offset/default/ignoreNull, etc.) + // Update status, returning the analysis result for the current row. Apply(args []any) any - // Reset 重置状态(Stop 或状态清理时调用)。 + // Reset: Resets the state (called during Stop or state cleanup). Reset() } -// StatefulAnalytic 由有状态分析函数(lag/latest/had_changed/changed_col/acc_*)实现。 -// NewState 创建一份独立状态,供状态机管理器为每个 PARTITION 各持一份。 +// StatefulAnalytic is implemented by stateful analysis functions (lag/latest/had_changed/changed_col/acc_*). +// NewState creates an independent state for the state machine manager to hold for each PARTITION. type StatefulAnalytic interface { NewState() AnalyticState } -// MultiColumnState 由输出多列的分析函数(changed_cols)实现。引擎对 MultiColumn -// 字段走 ApplyColumns:传入 prefix/ignoreNull 与 {列名: 当前值},返回 {prefix+列名: 新值} -// 仅含发生变化的列。单列函数继续走 Apply。 +// MultiColumnState is implemented by outputting multi-column analysis functions (changed_cols). The engine is compatible with MultiColumn +// Fields go through ApplyColumns: Pass prefix/ignoreNull and {column_name: current value}, return {prefix+column_name: new} +// Only columns with changes are included. Single-column function continues with Apply. type MultiColumnState interface { ApplyColumns(prefix string, ignoreNull bool, cols map[string]any) map[string]any } -// NamedRowState 由按列名比较整行的分析函数(had_changed 用 '*' 时)实现。按列名比较 -// 避免行 schema 变化(列增删/乱序)时的位置错位。 +// NamedRowState is implemented by an analysis function that compares entire rows by column name (had_changed using '*'). Compare by list name +// Prevents misalignment when row schema changes (column addition, deletion, or disorder order). type NamedRowState interface { ApplyNamed(ignoreNull bool, cols map[string]any) any } -// analyticToInt 容错整数转换:lag offset 等参数经 parseFunctionArgs 后可能为 -// int/int64/float64,统一转 int。 +// analyticToInt: Fault-tolerant integer conversion: parameters like lag offset may be obtained after parseFunctionArgs +// int/int64/float64, uniformly converted to int. func analyticToInt(v any) (int, bool) { switch n := v.(type) { case int: @@ -51,8 +51,8 @@ func analyticToInt(v any) (int, bool) { return 0, false } -// AnalyticToBool 容错布尔转换:had_changed/changed_col(s) 的 ignoreNull 参数经 -// parseFunctionArgs 后可能为 bool 或字符串 "true"/"false"(未加引号的 true 落到字符串分支)。 +// AnalyticToBool fault-tolerant Boolean transform: The ignoreNull parameters of had_changed/changed_col(s) are used +// parseFunctionArgs may be bool or string "true"/"false" (unquoted true falls into the string branch). func AnalyticToBool(v any) bool { switch b := v.(type) { case bool: @@ -63,7 +63,7 @@ func AnalyticToBool(v any) bool { return false } -// analyticEqual 值相等比较,数字跨类型(int vs float64)判等,其余 reflect.DeepEqual。 +// analyticEqual values are equal, numbers cross-type (int vs float64) are equal, the rest reflect.DeepEqual. func analyticEqual(a, b any) bool { if a == nil || b == nil { return a == nil && b == nil @@ -76,7 +76,7 @@ func analyticEqual(a, b any) bool { return reflect.DeepEqual(a, b) } -// toFloat64Generic 把数字类型统一到 float64 用于相等/比较。 +// toFloat64Generic unifies numeric types to float64 for equality/comparison. func toFloat64Generic(v any) (float64, bool) { switch n := v.(type) { case int: diff --git a/functions/base.go b/functions/base.go index 0f53067..7a1db9d 100644 --- a/functions/base.go +++ b/functions/base.go @@ -87,8 +87,8 @@ func (bf *BaseFunction) ValidateArgCount(args []any) error { return nil } -// Validate 提供默认校验(仅检查参数个数)。嵌入 BaseFunction 的函数若无需额外校验, -// 可省略 Validate;需要类型/取值校验时直接覆盖此方法。 +// Validate provides default validation (only checks the number of parameters). If a function embedded in BaseFunction does not require additional validation, +// Validate can be omitted; When type/value validation is required, this method is overridden directly. func (bf *BaseFunction) Validate(args []any) error { return bf.ValidateArgCount(args) } diff --git a/functions/custom_function_test.go b/functions/custom_function_test.go index 5134ba3..1459cfa 100644 --- a/functions/custom_function_test.go +++ b/functions/custom_function_test.go @@ -69,13 +69,13 @@ func TestCreateAggregator(t *testing.T) { } func TestAggregatorAdapter(t *testing.T) { - // 测试聚合器适配器 + // Test the aggregator adapter adapter, err := NewAggregatorAdapter("sum") if err != nil { t.Fatalf("Failed to create aggregator adapter: %v", err) } - // 测试创建新实例 + // Test to create new instances newInstance := adapter.New() if newInstance == nil { t.Fatal("Failed to create new adapter instance") @@ -86,7 +86,7 @@ func TestAggregatorAdapter(t *testing.T) { t.Fatal("New instance is not an AggregatorAdapter") } - // 测试添加值和获取结果 + // Test the added value and obtain the results newAdapter.Add(5.0) newAdapter.Add(10.0) @@ -97,10 +97,10 @@ func TestAggregatorAdapter(t *testing.T) { } func TestCustomFunctionRegistration(t *testing.T) { - // 注册自定义函数示例 + // Register custom function examples RegisterCustomFunctions() - // 测试自定义聚合函数 + // Test custom aggregate functions productFunc, exists := Get("product") if !exists { t.Fatal("Custom product function not registered") @@ -110,7 +110,7 @@ func TestCustomFunctionRegistration(t *testing.T) { t.Error("Product function should be aggregation type") } - // 测试自定义分析函数 + // Test the custom analysis function movingAvgFunc, exists := Get("moving_avg") if !exists { t.Fatal("Custom moving average function not registered") @@ -120,7 +120,7 @@ func TestCustomFunctionRegistration(t *testing.T) { t.Error("Moving average function should be analytical type") } - // 测试简单自定义函数 + // Test simple custom functions doubleFunc, exists := Get("double") if !exists { t.Fatal("Custom double function not registered") @@ -140,13 +140,13 @@ func TestCustomFunctionRegistration(t *testing.T) { } func TestFunctionRegistry(t *testing.T) { - // 测试函数注册表 + // Test the function registry allFunctions := ListAll() if len(allFunctions) == 0 { t.Error("No functions registered") } - // 测试按类型获取函数 + // Test to get functions by type aggFunctions := GetByType(TypeAggregation) if len(aggFunctions) == 0 { t.Error("No aggregation functions found") @@ -157,7 +157,7 @@ func TestFunctionRegistry(t *testing.T) { t.Error("No analytical functions found") } - // 验证一些内置函数存在 + // Verify the existence of some built-in functions expectedFunctions := []string{"sum", "avg", "min", "max", "count", "lag", "latest"} for _, funcName := range expectedFunctions { if _, exists := Get(funcName); !exists { @@ -183,7 +183,7 @@ func BenchmarkAggregatorBatch(b *testing.B) { Data: make(map[string]any), } - // 准备测试数据 + // Prepare test data args := make([]any, b.N) for i := 0; i < b.N; i++ { args[i] = float64(i) @@ -193,7 +193,7 @@ func BenchmarkAggregatorBatch(b *testing.B) { _, _ = sumFunc.Execute(ctx, args) } -// CustomProductFunction 自定义乘积聚合函数示例 +// Example of a custom product aggregation function with CustomProductFunction type CustomProductFunction struct { *BaseFunction product float64 @@ -224,7 +224,7 @@ func (f *CustomProductFunction) Execute(ctx *FunctionContext, args []any) (any, return product, nil } -// 实现AggregatorFunction接口 +// Implement the AggregatorFunction interface func (f *CustomProductFunction) New() AggregatorFunction { return &CustomProductFunction{ BaseFunction: f.BaseFunction, @@ -264,9 +264,9 @@ func (f *CustomProductFunction) Clone() AggregatorFunction { } } -// CustomMovingAverageFunction 自定义移动平均分析函数示例。 -// 演示自定义分析函数的正确写法:实现 StatefulAnalytic(NewState→Apply),跨行状态 -// 放在独立 State 里,由 AnalyticEngine 为每个 PARTITION 各持一份逐条求值。 +// CustomMovingAverageFunction: Example of a custom moving average analysis function. +// Demonstrates the correct way to write custom analysis functions: implement StatefulAnalytic(NewState→Apply) and span lines +// Placed in independent states, AnalyticEngine holds a copy for each PARTITION and evaluates each entry. type CustomMovingAverageFunction struct { *BaseFunction windowSize int @@ -284,17 +284,17 @@ func (f *CustomMovingAverageFunction) Validate(args []any) error { return f.ValidateArgCount(args) } -// Execute 标量路径禁用:分析函数需跨行状态,由 AnalyticEngine 的状态机求值。 +// Execute scalar path disabled: The analysis function needs to be cross-line state, evaluated by the state machine of AnalyticEngine. func (f *CustomMovingAverageFunction) Execute(ctx *FunctionContext, args []any) (any, error) { return nil, fmt.Errorf("analytic function %q must be used as a field or with OVER, not in a scalar expression", f.GetName()) } -// NewState 实现 StatefulAnalytic:每个 PARTITION 一份独立窗口状态。 +// NewState implements StatefulAnalytic: Each PARTITION has its own independent window state. func (f *CustomMovingAverageFunction) NewState() AnalyticState { return &movingAvgState{windowSize: f.windowSize} } -// movingAvgState 维护最近 windowSize 个值,Apply 返回当前窗口均值。 +// movingAvgState maintains the value of the most recent windowSize, and Apply returns the current window's average. type movingAvgState struct { windowSize int values []float64 @@ -321,7 +321,7 @@ func (s *movingAvgState) Apply(args []any) any { func (s *movingAvgState) Reset() { s.values = nil } -// CustomGeometricMeanFunction 自定义几何平均聚合函数示例 +// CustomGeometricMeanFunction is an example custom geometric-mean aggregation function. type CustomGeometricMeanFunction struct { *BaseFunction product float64 @@ -355,7 +355,7 @@ func (f *CustomGeometricMeanFunction) Execute(ctx *FunctionContext, args []any) return math.Pow(product, 1.0/float64(len(args))), nil } -// 实现AggregatorFunction接口 +// Implement the AggregatorFunction interface func (f *CustomGeometricMeanFunction) New() AggregatorFunction { return &CustomGeometricMeanFunction{ BaseFunction: f.BaseFunction, @@ -391,20 +391,20 @@ func (f *CustomGeometricMeanFunction) Clone() AggregatorFunction { } } -// RegisterCustomFunctions 注册自定义函数的示例 +// RegisterCustomFunctions Example of registering custom functions func RegisterCustomFunctions() { - // 注册自定义聚合函数 + // Register custom aggregation functions _ = Register(NewCustomProductFunction()) _ = Register(NewCustomGeometricMeanFunction()) - // 注册自定义分析函数 - _ = Register(NewCustomMovingAverageFunction(5)) // 5个值的移动平均 + // Register custom analysis functions + _ = Register(NewCustomMovingAverageFunction(5)) // Moving average of 5 values - // 注册适配器 + // Register the adapter RegisterAggregatorAdapter("product") RegisterAggregatorAdapter("geomean") - // 使用RegisterCustomFunction的方式注册简单函数 + // Register simple functions using the RegisterCustomFunction method RegisterCustomFunction("double", TypeMath, "自定义函数", "将值乘以2", 1, 1, func(ctx *FunctionContext, args []any) (any, error) { val, err := cast.ToFloat64E(args[0]) diff --git a/functions/expr_bridge.go b/functions/expr_bridge.go index 9f930c1..ce25dab 100644 --- a/functions/expr_bridge.go +++ b/functions/expr_bridge.go @@ -13,9 +13,9 @@ import ( "github.com/rulego/streamsql/utils/cast" ) -// exprLangBuiltinNames 是 expr-lang/expr 自带、且与 StreamSQL 函数不冲突的内置函数名, -// 作为单一事实源供 ResolveFunction / IsExprLangFunction / GetFunctionInfo 共用。 -// concat 等与 StreamSQL 同名的函数不列入(StreamSQL 注册优先)。 +// exprLangBuiltinNames are built-in function names in expr-lang/expr that do not conflict with StreamSQL functions. +// Serves as a single source of fact shared by ResolveFunction / IsExprLangFunction / GetFunctionInfo. +// Functions with the same name as StreamSQL, such as concat, are not included (StreamSQL registration takes priority). var exprLangBuiltinNames = []string{ "abs", "ceil", "floor", "round", "max", "min", "trim", "upper", "lower", "split", "replace", "indexOf", "hasPrefix", "hasSuffix", @@ -170,7 +170,7 @@ func (bridge *ExprBridge) preprocessCached(expression string) string { return result } -// CompileExpressionWithStreamSQLFunctions 编译表达式,包含StreamSQL函数 +// CompileExpressionWithStreamSQLFunctions compiled expression, including the StreamSQL function func (bridge *ExprBridge) CompileExpressionWithStreamSQLFunctions(expression string, dataType any) (*vm.Program, error) { // Cache compiled programs by expression source. A program is reusable while // the env type is unchanged, so the entry stores the reflect.Type it was @@ -188,11 +188,11 @@ func (bridge *ExprBridge) CompileExpressionWithStreamSQLFunctions(expression str expr.Env(dataType), } - // 添加StreamSQL函数 + // Add the StreamSQL function streamSQLOptions := bridge.RegisterStreamSQLFunctionsToExpr() options = append(options, streamSQLOptions...) - // 添加LIKE相关的自定义函数(只需要like_match,其他是内置操作符) + // Add custom functions related to LIKE (only like_match; others are built-in operators) options = append(options, expr.Function("like_match", func(params ...any) (any, error) { if len(params) != 2 { @@ -207,10 +207,10 @@ func (bridge *ExprBridge) CompileExpressionWithStreamSQLFunctions(expression str }), ) - // 启用一些有用的expr功能 + // Enable some useful expr features options = append(options, - expr.AllowUndefinedVariables(), // 允许未定义变量 - // 移除 expr.AsBool() 以允许返回任意类型的值 + expr.AllowUndefinedVariables(), // Allows undefined variables + // Remove expr.AsBool() to allow returns of values of any type ) program, err := expr.Compile(expression, options...) @@ -228,12 +228,12 @@ type progCacheEntry struct { prog *vm.Program } -// EvaluateExpression 评估表达式,自动选择最合适的引擎 +// EvaluateExpression evaluates expressions and automatically selects the most suitable engine func (bridge *ExprBridge) EvaluateExpression(expression string, data map[string]any) (any, error) { - // 预处理(反引号 / LIKE / IS NULL):仅依赖表达式文本,按输入表达式缓存。 + // Preprocessing (backquotes / LIKE / IS NULL): relies solely on expression text, cached by input expression. expression = bridge.preprocessCached(expression) - // 检查是否包含字符串拼接模式 + // Check whether string concatenation mode is included if bridge.isStringConcatenationExpression(expression, data) { result, err := bridge.evaluateStringConcatenation(expression, data) if err == nil { @@ -241,9 +241,9 @@ func (bridge *ExprBridge) EvaluateExpression(expression string, data map[string] } } - // expr() 在运行期对当行数据求值动态子表达式。编译路径把 StreamSQL 函数烘焙进 - // program 时,闭包的 ctx.Data 只携带函数包装、不含行数据,因此 expr() 必须走 - // env 路径(其闭包捕获真实 data)。其余表达式走快速编译路径。 + // expr() evaluates dynamic subexpressions for the current row during runtime. The compilation path bakes the StreamSQL function into the compilation path + // program, the closure's ctx.Data only carries function wrappers and does not include row data, so expr() must be used + // env path (whose closure captures the real data). The remaining expressions follow a fast compilation path. if !bridge.usesExprFunction(expression) { program, err := bridge.CompileExpressionWithStreamSQLFunctions(expression, data) if err == nil { @@ -259,15 +259,15 @@ func (bridge *ExprBridge) EvaluateExpression(expression string, data map[string] } } - // env 路径:expr() 的正确路径,也是编译失败时的回退。 + // env path: The correct path to expr(), and also the fallback when compilation fails. env := bridge.CreateEnhancedExprEnvironment(data) result, err := expr.Eval(expression, env) if err != nil { - // 检查是否是函数调用,如果是则不要回退到数值表达式处理 + // Check whether it is a function call; if so, do not revert to numeric expression processing if bridge.isFunctionCall(expression) { return nil, fmt.Errorf("failed to evaluate function call '%s': %v", expression, err) } - // 如果expr失败,回退到自定义expr系统(仅限数值计算) + // If expr fails, it falls back to a custom expr system (only numerical calculations). return bridge.fallbackToCustomExpr(expression, data) } @@ -286,26 +286,26 @@ func (bridge *ExprBridge) usesExprFunction(expression string) bool { return exprCallPattern.MatchString(expression) } -// isStringConcatenationExpression 检查是否是字符串拼接表达式 +// isStringConcatenationExpression checks whether it is a string concatenation expression func (bridge *ExprBridge) isStringConcatenationExpression(expression string, data map[string]any) bool { - // 如果表达式包含 + 操作符 + // If the expression contains the + operator if !strings.Contains(expression, "+") { return false } - // 分析表达式中的操作数 + // Analyze operands in the expression parts := strings.Split(expression, "+") for _, part := range parts { part = strings.TrimSpace(part) - // 如果包含字符串字面量(用引号包围) + // If string literals are included (enclosed in quotes) if (strings.HasPrefix(part, "'") && strings.HasSuffix(part, "'")) || (strings.HasPrefix(part, "\"") && strings.HasSuffix(part, "\"")) || part == "_" { return true } - // 如果是字段引用,检查字段值是否为字符串 + // If it is a field reference, check whether the field value is a string if value, exists := data[part]; exists { if _, isString := value.(string); isString { return true @@ -316,15 +316,15 @@ func (bridge *ExprBridge) isStringConcatenationExpression(expression string, dat return false } -// fallbackToCustomExpr 回退到自定义表达式系统 +// fallbackToCustomExpr falls back to the custom expression system func (bridge *ExprBridge) fallbackToCustomExpr(expression string, data map[string]any) (any, error) { - // 尝试处理字符串拼接表达式 + // Try handling string concatenation expressions result, err := bridge.evaluateStringConcatenation(expression, data) if err == nil { return result, nil } - // 如果不是字符串拼接,尝试简单的数值表达式 + // If it's not string concatenation, try simple numeric expressions numResult, err := bridge.evaluateSimpleNumericExpression(expression, data) if err == nil { return numResult, nil @@ -333,22 +333,22 @@ func (bridge *ExprBridge) fallbackToCustomExpr(expression string, data map[strin return nil, fmt.Errorf("unable to evaluate expression: %s, string concat error: %v, numeric error: %v", expression, err, err) } -// evaluateStringConcatenation 处理字符串拼接表达式 +// evaluateStringConcatenation handles string concatenation expressions func (bridge *ExprBridge) evaluateStringConcatenation(expression string, data map[string]any) (any, error) { - // 检查是否是字符串拼接表达式 (包含 + 和字符串字面量) + // Check if it is a string concatenation expression (including + and string literals) if !strings.Contains(expression, "+") { return nil, fmt.Errorf("not a concatenation expression") } - // 简单的字符串拼接解析器 - // 支持格式: field1 + 'literal' + field2 或 field1 + "_" + field2 + // A simple string concatenation parser + // Supported formats: field1 + 'literal' + field2 or field1 + "_" + field2 parts := strings.Split(expression, "+") var result strings.Builder for _, part := range parts { part = strings.TrimSpace(part) - // 处理字符串字面量 (用单引号包围) + // Handling string literals (enclosed in single quotes) if strings.HasPrefix(part, "'") && strings.HasSuffix(part, "'") { literal := strings.Trim(part, "'") result.WriteString(literal) @@ -356,10 +356,10 @@ func (bridge *ExprBridge) evaluateStringConcatenation(expression string, data ma literal := strings.Trim(part, "\"") result.WriteString(literal) } else if part == "_" { - // 处理下划线字面量 + // Handle underlined literal quantities result.WriteString("_") } else { - // 处理字段引用 + // Processing field references if value, exists := data[part]; exists { strValue := cast.ToString(value) result.WriteString(strValue) @@ -372,21 +372,21 @@ func (bridge *ExprBridge) evaluateStringConcatenation(expression string, data ma return result.String(), nil } -// evaluateSimpleNumericExpression 处理简单的数值表达式 +// evaluateSimpleNumericExpression handles simple numerical expressions func (bridge *ExprBridge) evaluateSimpleNumericExpression(expression string, data map[string]any) (any, error) { expression = strings.TrimSpace(expression) - // 处理简单的字段引用 + // Handle simple field references if value, exists := data[expression]; exists { return value, nil } - // 处理数字字面量 + // Handle numerical literal quantities if num, err := strconv.ParseFloat(expression, 64); err == nil { return num, nil } - // 处理简单的数学运算 (例如: field * 2, field + 5) + // Handling simple mathematical operations (e.g., field * 2, field + 5) for _, op := range []string{"+", "-", "*", "/"} { if strings.Contains(expression, op) { parts := strings.Split(expression, op) @@ -394,7 +394,7 @@ func (bridge *ExprBridge) evaluateSimpleNumericExpression(expression string, dat left := strings.TrimSpace(parts[0]) right := strings.TrimSpace(parts[1]) - // 获取左值 + // Obtain the lvalue var leftVal float64 if val, exists := data[left]; exists { if f, err := bridge.toFloat64(val); err == nil { @@ -405,10 +405,10 @@ func (bridge *ExprBridge) evaluateSimpleNumericExpression(expression string, dat } else if f, err := strconv.ParseFloat(left, 64); err == nil { leftVal = f } else { - continue // 尝试下一个操作符 + continue // Try the next operator } - // 获取右值 + // Get an rvalue var rightVal float64 if val, exists := data[right]; exists { if f, err := bridge.toFloat64(val); err == nil { @@ -419,10 +419,10 @@ func (bridge *ExprBridge) evaluateSimpleNumericExpression(expression string, dat } else if f, err := strconv.ParseFloat(right, 64); err == nil { rightVal = f } else { - continue // 尝试下一个操作符 + continue // Try the next operator } - // 执行运算 + // Perform the calculation switch op { case "+": return leftVal + rightVal, nil @@ -443,45 +443,45 @@ func (bridge *ExprBridge) evaluateSimpleNumericExpression(expression string, dat return nil, fmt.Errorf("unsupported expression: %s", expression) } -// ContainsLikeOperator 检查表达式是否包含LIKE操作符 +// ContainsLikeOperator checks whether the expression contains the LIKE operator func (bridge *ExprBridge) ContainsLikeOperator(expression string) bool { - // 简单检查是否包含LIKE关键字 + // A simple check to see if the LIKE keyword is included upperExpr := strings.ToUpper(expression) return strings.Contains(upperExpr, " LIKE ") } -// ContainsIsNullOperator 检查表达式是否包含IS NULL或IS NOT NULL操作符 +// ContainsIsNullOperator checks whether the expression contains the IS NULL or IS NOT NULL operator func (bridge *ExprBridge) ContainsIsNullOperator(expression string) bool { upperExpr := strings.ToUpper(expression) return strings.Contains(upperExpr, " IS NULL") || strings.Contains(upperExpr, " IS NOT NULL") } -// isFunctionCall 检查表达式是否是函数调用 +// isFunctionCall checks whether the expression is a function call func (bridge *ExprBridge) isFunctionCall(expression string) bool { - // 如果是CASE表达式,则不是函数调用 + // If it is a CASE expression, it is not a function call trimmed := strings.TrimSpace(expression) upperTrimmed := strings.ToUpper(trimmed) if strings.HasPrefix(upperTrimmed, "CASE ") || strings.HasPrefix(upperTrimmed, "CASE\t") || strings.HasPrefix(upperTrimmed, "CASE\n") { return false } - // 检查是否符合简单函数调用模式: function_name(args) - // 函数调用应该以标识符开始,后跟括号 + // Check if it matches the simple function call pattern: function_name(args) + // Function calls should start with an identifier, followed by parentheses if !strings.Contains(expression, "(") || !strings.Contains(expression, ")") { return false } - // 检查是否以标识符开始(函数名) + // Check if it starts with an identifier (function name) for i, r := range trimmed { if i == 0 { if !((r >= 'a' && r <= 'z') || (r >= 'A' && r <= 'Z') || r == '_') { return false } } else if r == '(' { - // 找到了开括号,说明这可能是函数调用 + // Finding the open parentheses means this may be a function call return true } else if !((r >= 'a' && r <= 'z') || (r >= 'A' && r <= 'Z') || (r >= '0' && r <= '9') || r == '_') { - // 遇到了非标识符字符且不是开括号,说明不是简单函数调用 + // If you encounter a non-identifier character that is not in open parentheses, it means it is not a simple function call return false } } @@ -489,150 +489,150 @@ func (bridge *ExprBridge) isFunctionCall(expression string) bool { return false } -// PreprocessLikeExpression 预处理LIKE表达式,转换为expr-lang可理解的函数调用 +// PreprocessLikeExpression: Preprocesses the LIKE expression, converting it into a function call understandable by expr-lang func (bridge *ExprBridge) PreprocessLikeExpression(expression string) (string, error) { - // 使用正则表达式匹配LIKE模式 - // 匹配: field LIKE 'pattern' 或 `field` LIKE 'pattern' (允许空模式) - // 支持反引号标识符和普通标识符 + // Use regular expressions to match the LIKE pattern + // Match: `field` LIKE 'pattern' or 'field' LIKE 'pattern' (allow null mode) + // Supports backquote identifiers and standard identifiers likePattern := `((?:` + "`" + `[^` + "`" + `]+` + "`" + `|\w+)(?:\.(?:` + "`" + `[^` + "`" + `]+` + "`" + `|\w+))*)\s+LIKE\s+'([^']*)'` re, err := regexp.Compile(likePattern) if err != nil { return expression, err } - // 替换所有LIKE表达式 + // Replace all LIKE expressions result := re.ReplaceAllStringFunc(expression, func(match string) string { submatches := re.FindStringSubmatch(match) if len(submatches) != 3 { - return match // 保持原样 + return match // Keep it as is } field := submatches[1] pattern := submatches[2] - // 处理反引号标识符,去除反引号 + // Handle backquote identifiers and remove backquotes if len(field) >= 2 && field[0] == '`' && field[len(field)-1] == '`' { - field = field[1 : len(field)-1] // 去掉反引号 + field = field[1 : len(field)-1] // Remove the quotation marks } - // 将LIKE模式转换为相应的函数调用 + // Convert the LIKE pattern into the corresponding function call return bridge.convertLikeToFunction(field, pattern) }) return result, nil } -// PreprocessIsNullExpression 预处理IS NULL和IS NOT NULL表达式,转换为expr-lang可理解的表达式 +// PreprocessIsNullExpression preprocesses IS NULL and IS NOT NULL expressions, converting them into expressions understandable by expr-lang func (bridge *ExprBridge) PreprocessIsNullExpression(expression string) (string, error) { - // 匹配复杂表达式的 IS NOT NULL 模式 (如函数调用) + // Matching complex expressions in IS NOT NULL pattern (such as function calls) complexNotNullPattern := `([A-Za-z_][A-Za-z0-9_]*\s*\([^)]*\))\s+IS\s+NOT\s+NULL` reComplexNotNull, err := regexp.Compile(complexNotNullPattern) if err != nil { return expression, err } - // 先处理复杂表达式的IS NOT NULL + // First, handle the complex expression IS NOT NULL result := reComplexNotNull.ReplaceAllString(expression, "is_not_null($1)") - // 匹配复杂表达式的 IS NULL 模式 + // Matches the IS NULL pattern for complex expressions complexNullPattern := `([A-Za-z_][A-Za-z0-9_]*\s*\([^)]*\))\s+IS\s+NULL` reComplexNull, err := regexp.Compile(complexNullPattern) if err != nil { return result, err } - // 处理复杂表达式的IS NULL + // Handles complex expressions with IS NULL result = reComplexNull.ReplaceAllString(result, "is_null($1)") - // 匹配简单字段的 IS NOT NULL 模式 (必须在复杂表达式之后处理) + // Matching simple fields in IS NOT NULL pattern (must be processed after complex expressions) isNotNullPattern := `(\w+(?:\.\w+)*)\s+IS\s+NOT\s+NULL` reNotNull, err := regexp.Compile(isNotNullPattern) if err != nil { return result, err } - // 替换简单字段的IS NOT NULL + // Replace the simple field IS NOT NULL result = reNotNull.ReplaceAllString(result, "$1 != nil") - // 匹配简单字段的 IS NULL 模式 + // Matches the IS NULL pattern for simple fields isNullPattern := `(\w+(?:\.\w+)*)\s+IS\s+NULL` reNull, err := regexp.Compile(isNullPattern) if err != nil { return result, err } - // 再替换简单字段的IS NULL + // Then replace the simple field IS NULL result = reNull.ReplaceAllString(result, "$1 == nil") return result, nil } -// ContainsBacktickIdentifiers 检查表达式是否包含反引号标识符 +// ContainsBacktickIdentifiers Check if the expression contains backtick identifiers func (bridge *ExprBridge) ContainsBacktickIdentifiers(expression string) bool { return strings.Contains(expression, "`") } -// PreprocessBacktickIdentifiers 预处理反引号标识符,去除反引号 +// PreprocessBacktickIdentifiers Preprocesses backquote identifiers and removes backquotes func (bridge *ExprBridge) PreprocessBacktickIdentifiers(expression string) (string, error) { - // 使用正则表达式匹配反引号标识符 - // 匹配: `identifier` 或 `nested.field` + // Use regular expressions to match backquote identifiers + // Match: `identifier` or `nested.field` backtickPattern := "`([^`]+)`" re, err := regexp.Compile(backtickPattern) if err != nil { return expression, err } - // 替换所有反引号标识符,去除反引号 + // Replace all backquote identifiers and remove backquotes result := re.ReplaceAllString(expression, "$1") return result, nil } -// convertLikeToFunction 将LIKE模式转换为expr-lang操作符 +// convertLikeToFunction converts LIKE mode to the expr-lang operator func (bridge *ExprBridge) convertLikeToFunction(field, pattern string) string { - // 处理空模式 + // Handle the null mode if pattern == "" { return fmt.Sprintf("%s == ''", field) } - // 含 _ 或内部 % 的模式必须走完整匹配器,startsWith/endsWith/contains - // 快速路径会把这些通配符当作字面量处理。 + // Patterns containing _ or internal % must use the full matcher, startsWith/endsWith/contains + // Fastpaths treat these wildcards as literals. core := strings.Trim(pattern, "%") if core != "" && (strings.Contains(core, "_") || strings.Contains(core, "%")) { return fmt.Sprintf("like_match(%s, '%s')", field, pattern) } - // 分析模式类型 + // Analyze the types of patterns if strings.HasPrefix(pattern, "%") && strings.HasSuffix(pattern, "%") && len(pattern) > 1 { - // %pattern% -> contains操作符(但不是单独的%) + // %pattern% -> contains operator (but not a standalone %) inner := strings.Trim(pattern, "%") if inner == "" { - // %% 表示匹配任何字符串 + // %% means matches any string return "true" } return fmt.Sprintf("%s contains '%s'", field, inner) } else if strings.HasPrefix(pattern, "%") && len(pattern) > 1 { - // %pattern -> endsWith操作符 + // %pattern -> endsWith operator suffix := strings.TrimPrefix(pattern, "%") return fmt.Sprintf("%s endsWith '%s'", field, suffix) } else if strings.HasSuffix(pattern, "%") && len(pattern) > 1 { - // pattern% -> startsWith操作符 + // pattern% -> startsWith operator prefix := strings.TrimSuffix(pattern, "%") return fmt.Sprintf("%s startsWith '%s'", field, prefix) } else if pattern == "%" { - // 单独的%匹配任何字符串 + // A single % matches any string return "true" } else if strings.Contains(pattern, "%") || strings.Contains(pattern, "_") { - // 复杂模式(如prefix%suffix)或包含单字符通配符,使用自定义的like_match函数 + // Complex patterns (such as prefix%suffix) or those containing single-character wildcards use custom like_match functions return fmt.Sprintf("like_match(%s, '%s')", field, pattern) } else { - // 精确匹配 + // Precise matching return fmt.Sprintf("%s == '%s'", field, pattern) } } -// matchesLikePattern 实现LIKE模式匹配 -// 支持%(匹配任意字符序列)和_(匹配单个字符)。 -// 采用经典双指针回溯算法,最坏 O(n*m),对抗性模式不会指数膨胀。 +// matchesLikePattern to achieve LIKE pattern matching +// Supports % (matches any character sequence) and _ (matches a single character). +// Using the classic double-pointer backtracking algorithm, the worst case is O(n*m), and the adversarial mode does not exponentially inflate. func (bridge *ExprBridge) matchesLikePattern(text, pattern string) bool { ti, pi := 0, 0 starIdx, matchIdx := -1, 0 @@ -658,7 +658,7 @@ func (bridge *ExprBridge) matchesLikePattern(text, pattern string) bool { return pi == len(pattern) } -// toFloat64 将值转换为float64 +// toFloat64 converts the value to float64 func (bridge *ExprBridge) toFloat64(val any) (float64, error) { switch v := val.(type) { case float64: @@ -678,16 +678,16 @@ func (bridge *ExprBridge) toFloat64(val any) (float64, error) { } } -// GetFunctionInfo 获取函数信息,统一两个系统的函数 +// GetFunctionInfo retrieves function information to unify the functions of the two systems func (bridge *ExprBridge) GetFunctionInfo() map[string]any { bridge.mutex.RLock() defer bridge.mutex.RUnlock() info := make(map[string]any) - // StreamSQL函数信息 - 动态获取所有当前注册的函数 + // StreamSQL Function Information - Dynamically retrieves all currently registered functions streamSQLFuncs := make(map[string]any) - allFunctions := ListAll() // 动态获取所有注册的函数 + allFunctions := ListAll() // Dynamically retrieves all registered functions for name, fn := range allFunctions { streamSQLFuncs[name] = map[string]any{ "name": fn.GetName(), @@ -699,9 +699,9 @@ func (bridge *ExprBridge) GetFunctionInfo() map[string]any { } info["streamsql"] = streamSQLFuncs - // expr-lang/expr内置函数(列出主要的) + // expr-lang/expr built-in functions (main ones listed) exprBuiltins := map[string]any{ - // 数学函数 + // Mathematical functions "abs": map[string]any{"category": "math", "description": "absolute value", "source": "expr-lang"}, "ceil": map[string]any{"category": "math", "description": "ceiling", "source": "expr-lang"}, "floor": map[string]any{"category": "math", "description": "floor", "source": "expr-lang"}, @@ -709,7 +709,7 @@ func (bridge *ExprBridge) GetFunctionInfo() map[string]any { "max": map[string]any{"category": "math", "description": "maximum", "source": "expr-lang"}, "min": map[string]any{"category": "math", "description": "minimum", "source": "expr-lang"}, - // 字符串函数 + // String function "trim": map[string]any{"category": "string", "description": "trim whitespace", "source": "expr-lang"}, "upper": map[string]any{"category": "string", "description": "to uppercase", "source": "expr-lang"}, "lower": map[string]any{"category": "string", "description": "to lowercase", "source": "expr-lang"}, @@ -719,7 +719,7 @@ func (bridge *ExprBridge) GetFunctionInfo() map[string]any { "hasPrefix": map[string]any{"category": "string", "description": "check prefix", "source": "expr-lang"}, "hasSuffix": map[string]any{"category": "string", "description": "check suffix", "source": "expr-lang"}, - // 数组/集合函数 + // Array/set functions "all": map[string]any{"category": "array", "description": "all elements satisfy", "source": "expr-lang"}, "any": map[string]any{"category": "array", "description": "any element satisfies", "source": "expr-lang"}, "filter": map[string]any{"category": "array", "description": "filter elements", "source": "expr-lang"}, @@ -728,22 +728,22 @@ func (bridge *ExprBridge) GetFunctionInfo() map[string]any { "count": map[string]any{"category": "array", "description": "count elements", "source": "expr-lang"}, "flatten": map[string]any{"category": "array", "description": "flatten array", "source": "expr-lang"}, - // 时间函数 + // Time function "now": map[string]any{"category": "datetime", "description": "current time", "source": "expr-lang"}, "duration": map[string]any{"category": "datetime", "description": "parse duration", "source": "expr-lang"}, "date": map[string]any{"category": "datetime", "description": "parse date", "source": "expr-lang"}, - // 类型转换 + // Type conversion "int": map[string]any{"category": "conversion", "description": "to integer", "source": "expr-lang"}, "float": map[string]any{"category": "conversion", "description": "to float", "source": "expr-lang"}, "string": map[string]any{"category": "conversion", "description": "to string", "source": "expr-lang"}, "type": map[string]any{"category": "conversion", "description": "get type", "source": "expr-lang"}, - // JSON处理 + // JSON processing "toJSON": map[string]any{"category": "json", "description": "to JSON", "source": "expr-lang"}, "fromJSON": map[string]any{"category": "json", "description": "from JSON", "source": "expr-lang"}, - // Base64编码 + // Base64 encoding "toBase64": map[string]any{"category": "encoding", "description": "to Base64", "source": "expr-lang"}, "fromBase64": map[string]any{"category": "encoding", "description": "from Base64", "source": "expr-lang"}, } @@ -752,21 +752,21 @@ func (bridge *ExprBridge) GetFunctionInfo() map[string]any { return info } -// ResolveFunction 解析函数调用,优先使用StreamSQL函数 +// ResolveFunction parsing function calls, preferably using the StreamSQL function func (bridge *ExprBridge) ResolveFunction(name string) (any, bool, string) { bridge.mutex.RLock() defer bridge.mutex.RUnlock() - // 进行大小写不敏感的查找 + // Perform case-insensitive searches lowerName := strings.ToLower(name) - // 首先检查StreamSQL函数(优先级更高) - 动态获取 + // First, check the StreamSQL function (higher priority) - Dynamic Acquisition allFunctions := ListAll() if fn, exists := allFunctions[lowerName]; exists { return fn, true, "streamsql" } - // 然后检查是否是expr-lang内置函数 + // Then check whether it is a built-in expr-lang function for _, b := range exprLangBuiltinNames { if strings.ToLower(b) == lowerName { return nil, true, "expr-lang" @@ -776,7 +776,7 @@ func (bridge *ExprBridge) ResolveFunction(name string) (any, bool, string) { return nil, false, "" } -// IsExprLangFunction 检查函数名是否是expr-lang内置函数 +// IsExprLangFunction checks whether the function name is an expr-lang built-in function func (bridge *ExprBridge) IsExprLangFunction(name string) bool { for _, b := range exprLangBuiltinNames { if b == name { @@ -786,13 +786,13 @@ func (bridge *ExprBridge) IsExprLangFunction(name string) bool { return false } -// 全局桥接器实例 +// Global Bridge Example var globalBridge *ExprBridge var globalBridgeMutex sync.RWMutex -// GetExprBridge 获取全局桥接器实例 +// GetExprBridge retrieves the global bridge instance func GetExprBridge() *ExprBridge { - // 首先使用读锁检查是否已初始化 + // First, use a lock read to check if initialization has been completed globalBridgeMutex.RLock() if globalBridge != nil { defer globalBridgeMutex.RUnlock() @@ -800,23 +800,23 @@ func GetExprBridge() *ExprBridge { } globalBridgeMutex.RUnlock() - // 使用写锁进行初始化 + // Initialization is performed using write locks globalBridgeMutex.Lock() defer globalBridgeMutex.Unlock() - // 双重检查模式,防止并发初始化 + // Dual-check mode prevents concurrent initialization if globalBridge == nil { globalBridge = NewExprBridge() } return globalBridge } -// 便捷函数:直接评估表达式 +// Convenience function: Directly evaluates expressions func EvaluateWithBridge(expression string, data map[string]any) (any, error) { return GetExprBridge().EvaluateExpression(expression, data) } -// 便捷函数:获取所有可用函数信息 +// Convenient functions: Get information on all available functions func GetAllAvailableFunctions() map[string]any { return GetExprBridge().GetFunctionInfo() } diff --git a/functions/expr_bridge_test.go b/functions/expr_bridge_test.go index ac64e61..58daa3a 100644 --- a/functions/expr_bridge_test.go +++ b/functions/expr_bridge_test.go @@ -35,18 +35,18 @@ func TestExprBridge(t *testing.T) { bridge := NewExprBridge() t.Run("StreamSQL Functions Available", func(t *testing.T) { - // 测试StreamSQL函数是否可用 + // Test whether the StreamSQL function is usable data := map[string]any{ "temperature": 25.5, "humidity": 60, } - // 测试数学函数 + // Test mathematical functions result, err := bridge.EvaluateExpression("abs(-5)", data) assert.NoError(t, err) assert.Equal(t, float64(5), result) - // 测试字符串函数 + // Test string function result, err = bridge.EvaluateExpression("length(\"hello\")", data) assert.NoError(t, err) assert.Equal(t, int(5), result) @@ -58,12 +58,12 @@ func TestExprBridge(t *testing.T) { "text": "Hello World", } - // 测试expr-lang数组函数 + // Test the expr-lang array function result, err := bridge.EvaluateExpression("len(numbers)", data) assert.NoError(t, err) assert.Equal(t, 5, result) - // 测试expr-lang字符串函数 + // Test the expr-lang string function result, err = bridge.EvaluateExpression("trim(\" hello \")", data) assert.NoError(t, err) assert.Equal(t, "hello", result) @@ -74,25 +74,25 @@ func TestExprBridge(t *testing.T) { "values": []float64{-3.5, 2.1, -1.8, 4.2}, } - // 使用StreamSQL的abs函数和expr-lang的filter函数 - // 注意:这个测试可能需要根据实际实现调整 + // Use StreamSQL's abs function and expr-lang's filter function + // Note: This test may need to be adjusted based on actual implementation env := bridge.CreateEnhancedExprEnvironment(data) - // 验证环境中包含所有预期的函数 + // The verification environment contains all expected functions assert.Contains(t, env, "abs") assert.Contains(t, env, "length") assert.Contains(t, env, "values") }) t.Run("Function Resolution", func(t *testing.T) { - // 测试函数解析优先级 + // Test function parsing priority _, exists, source := bridge.ResolveFunction("abs") assert.True(t, exists) - assert.Equal(t, "streamsql", source) // StreamSQL优先 + assert.Equal(t, "streamsql", source) // StreamSQL is the priority _, exists, source = bridge.ResolveFunction("encode") assert.True(t, exists) - assert.Equal(t, "streamsql", source) // StreamSQL独有 + assert.Equal(t, "streamsql", source) // Unique to StreamSQL _, exists, _ = bridge.ResolveFunction("nonexistent") assert.False(t, exists) @@ -101,13 +101,13 @@ func TestExprBridge(t *testing.T) { t.Run("Function Information", func(t *testing.T) { info := bridge.GetFunctionInfo() - // 验证包含StreamSQL函数信息 + // Verify that information about the StreamSQL function is included streamSQLFuncs, ok := info["streamsql"].(map[string]any) assert.True(t, ok) assert.Contains(t, streamSQLFuncs, "abs") assert.Contains(t, streamSQLFuncs, "encode") - // 验证包含expr-lang函数信息 + // Verify that the information containing the expr-lang function is included exprLangFuncs, ok := info["expr-lang"].(map[string]any) assert.True(t, ok) assert.Contains(t, exprLangFuncs, "trim") @@ -121,12 +121,12 @@ func TestEvaluateWithBridge(t *testing.T) { "y": -2.1, } - // 测试简单表达式 + // Test simple expressions result, err := EvaluateWithBridge("abs(y)", data) assert.NoError(t, err) assert.Equal(t, 2.1, result) - // 测试复合表达式 + // Test the composite expression result, err = EvaluateWithBridge("x + abs(y)", data) assert.NoError(t, err) assert.Equal(t, 5.6, result) @@ -135,21 +135,21 @@ func TestEvaluateWithBridge(t *testing.T) { func TestGetAllAvailableFunctions(t *testing.T) { info := GetAllAvailableFunctions() - // 验证返回的信息结构 + // Verify the returned information structure assert.Contains(t, info, "streamsql") assert.Contains(t, info, "expr-lang") - // 验证函数数量合理 + // The number of verification functions is reasonable streamSQLFuncs := info["streamsql"].(map[string]any) t.Logf("StreamSQL functions count: %d", len(streamSQLFuncs)) // for name := range streamSQLFuncs { // t.Logf("StreamSQL function: %s", name) // } - assert.GreaterOrEqual(t, len(streamSQLFuncs), 1) // 至少应该有一个函数 + assert.GreaterOrEqual(t, len(streamSQLFuncs), 1) // At the very least, there should be a function exprLangFuncs := info["expr-lang"].(map[string]any) t.Logf("Expr-lang functions count: %d", len(exprLangFuncs)) - assert.GreaterOrEqual(t, len(exprLangFuncs), 1) // 至少应该有一个函数 + assert.GreaterOrEqual(t, len(exprLangFuncs), 1) // At the very least, there should be a function } func TestFunctionConflictResolution(t *testing.T) { @@ -158,15 +158,15 @@ func TestFunctionConflictResolution(t *testing.T) { "value": -5.5, } - // 测试冲突函数的解析(abs函数在两个系统中都存在) - // 应该优先使用expr-lang的版本 + // Testing the parsing of the collision function (the abs function exists in both systems) + // The expr-lang version should be prioritized env := bridge.CreateEnhancedExprEnvironment(data) - // 验证StreamSQL函数可以通过别名访问 + // Verify that the StreamSQL function can be accessed with an alias assert.Contains(t, env, "streamsql_abs") assert.Contains(t, env, "abs") - // 测试两个版本都能正常工作 + // Both versions worked properly result, err := bridge.EvaluateExpression("abs(value)", data) assert.NoError(t, err) assert.Equal(t, 5.5, result) @@ -181,7 +181,7 @@ func TestExprBridgeAdvancedFunctions(t *testing.T) { "age": 25, } - // 测试字符串连接表达式检测 + // Test string concatenation expression detection isConcat := bridge.isStringConcatenationExpression("name + ' is ' + age", data) assert.True(t, isConcat) @@ -194,7 +194,7 @@ func TestExprBridgeAdvancedFunctions(t *testing.T) { "text": "hello", } - // 测试回退到自定义表达式处理 + // Test backward to custom expression processing result, err := bridge.fallbackToCustomExpr("text + ' world'", data) assert.NoError(t, err) assert.Equal(t, "hello world", result) @@ -206,7 +206,7 @@ func TestExprBridgeAdvancedFunctions(t *testing.T) { "second": "World", } - // 测试字符串连接求值 + // Test string connection evaluation result, err := bridge.evaluateStringConcatenation("first + ' ' + second", data) assert.NoError(t, err) assert.Equal(t, "Hello World", result) @@ -218,14 +218,14 @@ func TestExprBridgeAdvancedFunctions(t *testing.T) { "y": 5, } - // 测试简单数值表达式 + // Test simple numerical expressions result, err := bridge.evaluateSimpleNumericExpression("x + y", data) assert.NoError(t, err) assert.Equal(t, float64(15), result) }) t.Run("Function Call Detection", func(t *testing.T) { - // 测试函数调用检测 + // Test function call detection assert.True(t, bridge.isFunctionCall("abs(-5)")) assert.True(t, bridge.isFunctionCall("length('hello')")) assert.False(t, bridge.isFunctionCall("x + y")) @@ -233,43 +233,43 @@ func TestExprBridgeAdvancedFunctions(t *testing.T) { }) t.Run("Like Expression Preprocessing", func(t *testing.T) { - // 测试LIKE表达式预处理 + // Test LIKE expression preprocessing processed, err := bridge.PreprocessLikeExpression("name LIKE '%john%'") assert.NoError(t, err) assert.Contains(t, processed, "contains") }) t.Run("IsNull Expression Preprocessing", func(t *testing.T) { - // 测试IS NULL表达式预处理 + // Test the IS NULL expression preprocessing processed, err := bridge.PreprocessIsNullExpression("field IS NULL") assert.NoError(t, err) assert.Contains(t, processed, "== nil") }) t.Run("Backtick Identifiers", func(t *testing.T) { - // 测试反引号标识符检测 + // Test backquote identifier detection assert.True(t, bridge.ContainsBacktickIdentifiers("`field_name` = 1")) assert.False(t, bridge.ContainsBacktickIdentifiers("field_name = 1")) - // 测试反引号标识符预处理 + // Test backquote identifier preprocessing processed, err := bridge.PreprocessBacktickIdentifiers("`field_name` = 1") assert.NoError(t, err) assert.Contains(t, processed, "field_name") }) t.Run("Like Pattern Matching", func(t *testing.T) { - // 测试LIKE模式匹配 + // Test LIKE pattern matching assert.True(t, bridge.matchesLikePattern("hello", "h%")) assert.True(t, bridge.matchesLikePattern("world", "%d")) assert.False(t, bridge.matchesLikePattern("hello", "x%")) - // 测试LIKE匹配 + // Test LIKE matching assert.True(t, bridge.matchesLikePattern("hello", "h%o")) assert.False(t, bridge.matchesLikePattern("hello", "x%")) }) t.Run("Type Conversion", func(t *testing.T) { - // 测试类型转换 + // Test type conversion result, err := bridge.toFloat64(10) assert.NoError(t, err) assert.Equal(t, float64(10), result) @@ -283,7 +283,7 @@ func TestExprBridgeAdvancedFunctions(t *testing.T) { }) t.Run("Expr Lang Function Detection", func(t *testing.T) { - // 测试expr-lang函数检测 + // Test expr-lang function detection assert.True(t, bridge.IsExprLangFunction("trim")) assert.True(t, bridge.IsExprLangFunction("len")) assert.True(t, bridge.IsExprLangFunction("abs")) @@ -291,7 +291,7 @@ func TestExprBridgeAdvancedFunctions(t *testing.T) { }) t.Run("Like to Function Conversion", func(t *testing.T) { - // 测试LIKE转换为函数调用 + // Test LIKE is converted into a function call result := bridge.convertLikeToFunction("name", "%john%") assert.Contains(t, result, "contains") assert.Contains(t, result, "name") @@ -317,7 +317,7 @@ func TestConvertLikeUnderscoreRoute(t *testing.T) { assert.Contains(t, bridge.convertLikeToFunction("name", "%abc%"), "contains") } -// TestExprBridgeComplexExpressions 测试复杂表达式处理 +// TestExprBridgeComplexExpressions tests complex expression handling func TestExprBridgeComplexExpressions(t *testing.T) { bridge := NewExprBridge() diff --git a/functions/functions_aggregation.go b/functions/functions_aggregation.go index 688a10a..955ad98 100644 --- a/functions/functions_aggregation.go +++ b/functions/functions_aggregation.go @@ -126,7 +126,7 @@ func (f *AvgFunction) Execute(ctx *FunctionContext, args []any) (any, error) { return sum / float64(count), nil } -// 实现AggregatorFunction接口 +// Implement the AggregatorFunction interface func (f *AvgFunction) New() AggregatorFunction { return &AvgFunction{ BaseFunction: f.BaseFunction, @@ -187,7 +187,7 @@ func (f *MinFunction) Validate(args []any) error { } func (f *MinFunction) Execute(ctx *FunctionContext, args []any) (any, error) { - // 检查是否有nil参数 + // Check if there are nil parameters for _, arg := range args { if arg == nil { return nil, nil @@ -268,7 +268,7 @@ func (f *MaxFunction) Validate(args []any) error { } func (f *MaxFunction) Execute(ctx *FunctionContext, args []any) (any, error) { - // 检查是否有nil参数 + // Check if there are nil parameters for _, arg := range args { if arg == nil { return nil, nil @@ -330,7 +330,7 @@ func (f *MaxFunction) Clone() AggregatorFunction { } } -// CountFunction 计数函数 +// CountFunction type CountFunction struct { *BaseFunction count int @@ -356,7 +356,7 @@ func (f *CountFunction) Execute(ctx *FunctionContext, args []any) (any, error) { return int64(count), nil } -// 实现AggregatorFunction接口 +// Implement the AggregatorFunction interface func (f *CountFunction) New() AggregatorFunction { return &CountFunction{ BaseFunction: f.BaseFunction, @@ -365,7 +365,7 @@ func (f *CountFunction) New() AggregatorFunction { } func (f *CountFunction) Add(value any) { - // 增强的Add方法:忽略NULL值 + // Enhanced Add method: ignores NULL values if value != nil { f.count++ } @@ -386,12 +386,12 @@ func (f *CountFunction) Clone() AggregatorFunction { } } -// StdDevFunction 标准差函数(韦尔福德算法实现) +// StdDevFunction Standard Deviation Function (implemented by the Welford algorithm) type StdDevFunction struct { *BaseFunction count int mean float64 - m2 float64 // 平方差的累计值 + m2 float64 // The cumulative value of the difference of squares } func NewStdDevFunction() *StdDevFunction { @@ -405,7 +405,7 @@ func (f *StdDevFunction) Validate(args []any) error { } func (f *StdDevFunction) Execute(ctx *FunctionContext, args []any) (any, error) { - // 批量执行模式,回退到传统算法 + // Batch execution mode reverts to traditional algorithms sum := 0.0 count := 0 for _, arg := range args { @@ -431,7 +431,7 @@ func (f *StdDevFunction) Execute(ctx *FunctionContext, args []any) (any, error) return math.Sqrt(variance / float64(count)), nil } -// 实现AggregatorFunction接口 - 韦尔福德算法 +// Implementing the AggregatorFunction interface - Wilford's algorithm func (f *StdDevFunction) New() AggregatorFunction { return &StdDevFunction{ BaseFunction: f.BaseFunction, @@ -476,7 +476,7 @@ func (f *StdDevFunction) Clone() AggregatorFunction { } } -// MedianFunction 中位数函数 +// MedianFunction median function type MedianFunction struct { *BaseFunction } @@ -508,7 +508,7 @@ func (f *MedianFunction) Execute(ctx *FunctionContext, args []any) (any, error) return values[mid], nil } -// PercentileFunction 百分位数函数 +// PercentileFunction: Percentile function type PercentileFunction struct { *BaseFunction } @@ -553,7 +553,7 @@ func (f *PercentileFunction) Execute(ctx *FunctionContext, args []any) (any, err return values[index], nil } -// CollectFunction 收集函数 - 获取当前窗口所有消息的列值组成的数组 +// CollectFunction - An array composed of column values for all messages in the current window type CollectFunction struct { *BaseFunction values []any @@ -571,13 +571,13 @@ func (f *CollectFunction) Validate(args []any) error { } func (f *CollectFunction) Execute(ctx *FunctionContext, args []any) (any, error) { - // 直接返回所有参数组成的数组 + // Directly returns an array composed of all parameters result := make([]any, len(args)) copy(result, args) return result, nil } -// 实现AggregatorFunction接口 +// Implement the AggregatorFunction interface func (f *CollectFunction) New() AggregatorFunction { return &CollectFunction{ BaseFunction: f.BaseFunction, @@ -608,7 +608,7 @@ func (f *CollectFunction) Clone() AggregatorFunction { return newFunc } -// FirstValueFunction 首个值函数 - 返回组中第一行的值 +// FirstValueFunction - Returns the value of the first row in the group type FirstValueFunction struct { *BaseFunction firstValue any @@ -634,11 +634,11 @@ func (f *FirstValueFunction) Execute(ctx *FunctionContext, args []any) (any, err if len(args) == 0 { return nil, fmt.Errorf("function %s requires at least one argument", f.GetName()) } - // 返回第一个值 + // Return the first value return args[0], nil } -// 实现AggregatorFunction接口 +// Implement the AggregatorFunction interface func (f *FirstValueFunction) New() AggregatorFunction { return &FirstValueFunction{ BaseFunction: f.BaseFunction, @@ -671,7 +671,7 @@ func (f *FirstValueFunction) Clone() AggregatorFunction { } } -// LastValueFunction 最后值函数 - 返回组中最后一行的值 +// LastValueFunction - Returns the value of the last row in the group type LastValueFunction struct { *BaseFunction lastValue any @@ -695,11 +695,11 @@ func (f *LastValueFunction) Execute(ctx *FunctionContext, args []any) (any, erro if len(args) == 0 { return nil, fmt.Errorf("function %s requires at least one argument", f.GetName()) } - // 返回最后一个值 + // Return the last value return args[len(args)-1], nil } -// 实现AggregatorFunction接口 +// Implement the AggregatorFunction interface func (f *LastValueFunction) New() AggregatorFunction { return &LastValueFunction{ BaseFunction: f.BaseFunction, @@ -726,7 +726,7 @@ func (f *LastValueFunction) Clone() AggregatorFunction { } } -// MergeAggFunction 合并聚合函数 - 将组中的值合并为单个值 +// MergeAggFunction Merge Aggregator - Merge values from a group into a single value type MergeAggFunction struct { *BaseFunction values []any @@ -748,7 +748,7 @@ func (f *MergeAggFunction) Execute(ctx *FunctionContext, args []any) (any, error return nil, nil } - // 尝试合并为字符串 + // Try merging into a string var result strings.Builder for i, arg := range args { if i > 0 { @@ -759,7 +759,7 @@ func (f *MergeAggFunction) Execute(ctx *FunctionContext, args []any) (any, error return result.String(), nil } -// 实现AggregatorFunction接口 +// Implement the AggregatorFunction interface func (f *MergeAggFunction) New() AggregatorFunction { return &MergeAggFunction{ BaseFunction: f.BaseFunction, @@ -776,7 +776,7 @@ func (f *MergeAggFunction) Result() any { return nil } - // 尝试合并为字符串 + // Try merging into a string var result strings.Builder for i, arg := range f.values { if i > 0 { @@ -800,7 +800,7 @@ func (f *MergeAggFunction) Clone() AggregatorFunction { return newFunc } -// StdDevSFunction 样本标准差函数(韦尔福德算法实现) +// StdDevSFunction Sample Standard Deviation Function (Implemented by the Welford Algorithm) type StdDevSFunction struct { *BaseFunction count int @@ -819,7 +819,7 @@ func (f *StdDevSFunction) Validate(args []any) error { } func (f *StdDevSFunction) Execute(ctx *FunctionContext, args []any) (any, error) { - // 批量执行模式 + // Batch execution mode sum := 0.0 count := 0 for _, arg := range args { @@ -845,7 +845,7 @@ func (f *StdDevSFunction) Execute(ctx *FunctionContext, args []any) (any, error) return math.Sqrt(variance / float64(count-1)), nil } -// 实现AggregatorFunction接口 - 韦尔福德算法 +// Implementing the AggregatorFunction interface - Wilford's algorithm func (f *StdDevSFunction) New() AggregatorFunction { return &StdDevSFunction{ BaseFunction: f.BaseFunction, @@ -890,7 +890,7 @@ func (f *StdDevSFunction) Clone() AggregatorFunction { } } -// DeduplicateFunction 去重函数 +// DeduplicateFunction type DeduplicateFunction struct { *BaseFunction } @@ -920,7 +920,7 @@ func (f *DeduplicateFunction) Execute(ctx *FunctionContext, args []any) (any, er return result, nil } -// VarFunction 总体方差函数(韦尔福德算法实现) +// VarFunction (Implemented by the Wellford Algorithm) type VarFunction struct { *BaseFunction count int @@ -939,7 +939,7 @@ func (f *VarFunction) Validate(args []any) error { } func (f *VarFunction) Execute(ctx *FunctionContext, args []any) (any, error) { - // 批量执行模式 + // Batch execution mode sum := 0.0 count := 0 for _, arg := range args { @@ -965,7 +965,7 @@ func (f *VarFunction) Execute(ctx *FunctionContext, args []any) (any, error) { return variance / float64(count), nil } -// 实现AggregatorFunction接口 - 韦尔福德算法 +// Implementing the AggregatorFunction interface - Wilford's algorithm func (f *VarFunction) New() AggregatorFunction { return &VarFunction{ BaseFunction: f.BaseFunction, @@ -1009,7 +1009,7 @@ func (f *VarFunction) Clone() AggregatorFunction { } } -// VarSFunction 样本方差函数(韦尔福德算法实现) +// VarSFunction sample variance function (implemented by Welford's algorithm) type VarSFunction struct { *BaseFunction count int @@ -1028,7 +1028,7 @@ func (f *VarSFunction) Validate(args []any) error { } func (f *VarSFunction) Execute(ctx *FunctionContext, args []any) (any, error) { - // 批量执行模式 + // Batch execution mode sum := 0.0 count := 0 for _, arg := range args { @@ -1054,7 +1054,7 @@ func (f *VarSFunction) Execute(ctx *FunctionContext, args []any) (any, error) { return variance / float64(count-1), nil } -// 实现AggregatorFunction接口 - 韦尔福德算法 +// Implementing the AggregatorFunction interface - Wilford's algorithm func (f *VarSFunction) New() AggregatorFunction { return &VarSFunction{ BaseFunction: f.BaseFunction, @@ -1098,7 +1098,7 @@ func (f *VarSFunction) Clone() AggregatorFunction { } } -// 为StdDevFunction添加AggregatorFunction接口实现 +// Adds the AggregatorFunction interface implementation to StdDevFunction type StdDevAggregatorFunction struct { *BaseFunction values []float64 @@ -1137,14 +1137,14 @@ func (f *StdDevAggregatorFunction) Result() any { return 0.0 } - // 计算平均值 + // Calculate the average sum := 0.0 for _, v := range f.values { sum += v } mean := sum / float64(len(f.values)) - // 计算方差 + // Calculate variance variance := 0.0 for _, v := range f.values { variance += math.Pow(v-mean, 2) @@ -1166,7 +1166,7 @@ func (f *StdDevAggregatorFunction) Clone() AggregatorFunction { return clone } -// 为MedianFunction添加AggregatorFunction接口实现 +// Adds the AggregatorFunction interface implementation to MedianFunction type MedianAggregatorFunction struct { *BaseFunction values []float64 @@ -1229,7 +1229,7 @@ func (f *MedianAggregatorFunction) Clone() AggregatorFunction { return clone } -// 为PercentileFunction添加AggregatorFunction接口实现 +// Adds an AggregatorFunction interface implementation to PercentileFunction type PercentileAggregatorFunction struct { *BaseFunction values []float64 @@ -1240,7 +1240,7 @@ func NewPercentileAggregatorFunction() *PercentileAggregatorFunction { return &PercentileAggregatorFunction{ BaseFunction: NewBaseFunction("percentile", TypeAggregation, "聚合函数", "计算数值百分位数", 2, 2), values: make([]float64, 0), - p: 0.95, // 默认95%分位数 + p: 0.95, // Default: 95% percentile } } @@ -1296,9 +1296,9 @@ func (f *PercentileAggregatorFunction) Clone() AggregatorFunction { return clone } -// Init 实现 ParameterizedFunction:从 SQL 第二参数取分位 p。 -// percentile(field, p) 中 p∈[0,1] 为第二参数(args[1]),field 的数据经 Add 累积。 -// 未实现本接口时,窗口聚合走 CreateParameterizedAggregator 的兜底分支 New(),p 退化为默认 0.95。 +// Init implements ParameterizedFunction: takes the quantile p from the SQL second argument. +// In percentile(field, p), p∈[0,1] is the second parameter (args[1]), and the field data is accumulated by adding. +// When this interface is not implemented, window aggregation uses the backend branch New() of CreateParameterizedAggregator, and p degenerates to the default 0.95. func (f *PercentileAggregatorFunction) Init(args []any) error { if len(args) < 2 { return fmt.Errorf("percentile requires (field, p); got %v", args) @@ -1319,7 +1319,7 @@ func (f *PercentileAggregatorFunction) Init(args []any) error { return nil } -// 为CollectFunction添加AggregatorFunction接口实现 +// Adds the AggregatorFunction interface implementation to CollectFunction type CollectAggregatorFunction struct { *BaseFunction values []any @@ -1368,7 +1368,7 @@ func (f *CollectAggregatorFunction) Clone() AggregatorFunction { return clone } -// 为LastValueFunction添加AggregatorFunction接口实现 +// Adds the AggregatorFunction interface implementation to LastValueFunction type LastValueAggregatorFunction struct { *BaseFunction lastValue any @@ -1413,7 +1413,7 @@ func (f *LastValueAggregatorFunction) Clone() AggregatorFunction { } } -// 为MergeAggFunction添加AggregatorFunction接口实现 +// Adds the AggregatorFunction interface implementation for MergeAggFunction type MergeAggAggregatorFunction struct { *BaseFunction values []any @@ -1473,7 +1473,7 @@ func (f *MergeAggAggregatorFunction) Clone() AggregatorFunction { return clone } -// 为StdDevSFunction添加AggregatorFunction接口实现 +// Adds the AggregatorFunction interface implementation for StdDevSFunction type StdDevSAggregatorFunction struct { *BaseFunction values []float64 @@ -1514,19 +1514,19 @@ func (f *StdDevSAggregatorFunction) Result() any { return 0.0 } - // 计算平均值 + // Calculate the average sum := 0.0 for _, v := range f.values { sum += v } mean := sum / float64(len(f.values)) - // 计算样本方差 + // Calculate sample variance variance := 0.0 for _, v := range f.values { variance += math.Pow(v-mean, 2) } - variance = variance / float64(len(f.values)-1) // 样本标准差使用n-1 + variance = variance / float64(len(f.values)-1) // The sample standard deviation is used as n-1 return math.Sqrt(variance) } @@ -1544,7 +1544,7 @@ func (f *StdDevSAggregatorFunction) Clone() AggregatorFunction { return clone } -// 为DeduplicateFunction添加AggregatorFunction接口实现 +// Adds the AggregatorFunction interface implementation to DeduplicateFunction type DeduplicateAggregatorFunction struct { *BaseFunction seen map[string]bool @@ -1605,7 +1605,7 @@ func (f *DeduplicateAggregatorFunction) Clone() AggregatorFunction { return clone } -// 为VarFunction添加AggregatorFunction接口实现 +// Adds the AggregatorFunction interface implementation to VarFunction type VarAggregatorFunction struct { *BaseFunction values []float64 @@ -1646,19 +1646,19 @@ func (f *VarAggregatorFunction) Result() any { return 0.0 } - // 计算平均值 + // Calculate the average sum := 0.0 for _, v := range f.values { sum += v } mean := sum / float64(len(f.values)) - // 计算总体方差 + // Calculate the population variance variance := 0.0 for _, v := range f.values { variance += math.Pow(v-mean, 2) } - variance = variance / float64(len(f.values)) // 总体方差使用n + variance = variance / float64(len(f.values)) // The population variance is expressed using n return variance } @@ -1676,7 +1676,7 @@ func (f *VarAggregatorFunction) Clone() AggregatorFunction { return clone } -// 为VarSFunction添加AggregatorFunction接口实现 +// Adds the AggregatorFunction interface implementation for VarSFunction type VarSAggregatorFunction struct { *BaseFunction values []float64 @@ -1717,19 +1717,19 @@ func (f *VarSAggregatorFunction) Result() any { return 0.0 } - // 计算平均值 + // Calculate the average sum := 0.0 for _, v := range f.values { sum += v } mean := sum / float64(len(f.values)) - // 计算样本方差 + // Calculate sample variance variance := 0.0 for _, v := range f.values { variance += math.Pow(v-mean, 2) } - variance = variance / float64(len(f.values)-1) // 样本方差使用n-1 + variance = variance / float64(len(f.values)-1) // Sample variance is used in n-1 return variance } diff --git a/functions/functions_aggregation_test.go b/functions/functions_aggregation_test.go index 6937070..9bcb898 100644 --- a/functions/functions_aggregation_test.go +++ b/functions/functions_aggregation_test.go @@ -9,7 +9,7 @@ import ( func TestStdDevFunction(t *testing.T) { fn := NewStdDevFunction() ctx := &FunctionContext{} - // Execute 批量 + // Execute batch execution result, err := fn.Execute(ctx, []any{1.0, 2.0, 3.0, 4.0}) if err != nil { t.Errorf("Execute error: %v", err) @@ -128,7 +128,7 @@ func TestMedianFunction(t *testing.T) { fn := NewMedianFunction() ctx := &FunctionContext{} - // 测试奇数个元素 + // Test odd elements result, err := fn.Execute(ctx, []any{1.0, 3.0, 2.0}) if err != nil { t.Errorf("Execute error: %v", err) @@ -137,7 +137,7 @@ func TestMedianFunction(t *testing.T) { t.Errorf("Execute median result = %v, want 2.0", result) } - // 测试偶数个元素 + // Test an even number of elements result, err = fn.Execute(ctx, []any{1.0, 3.0, 2.0, 4.0}) if err != nil { t.Errorf("Execute error: %v", err) @@ -146,7 +146,7 @@ func TestMedianFunction(t *testing.T) { t.Errorf("Execute median result = %v, want 2.5", result) } - // 测试单个元素 + // Test individual elements result, err = fn.Execute(ctx, []any{5.0}) if err != nil { t.Errorf("Execute error: %v", err) @@ -160,7 +160,7 @@ func TestPercentileFunction(t *testing.T) { fn := NewPercentileFunction() ctx := &FunctionContext{} - // 测试50%分位数 + // Test 50% of quantiles result, err := fn.Execute(ctx, []any{0.5, 1.0, 2.0, 3.0, 4.0}) if err != nil { t.Errorf("Execute error: %v", err) @@ -169,7 +169,7 @@ func TestPercentileFunction(t *testing.T) { t.Errorf("Execute percentile result = %v, want 2.0", result) } - // 测试25%分位数 + // Testing 25% percentiles result, err = fn.Execute(ctx, []any{0.25, 1.0, 2.0, 3.0, 4.0}) if err != nil { t.Errorf("Execute error: %v", err) @@ -178,7 +178,7 @@ func TestPercentileFunction(t *testing.T) { t.Errorf("Execute percentile result = %v, want 1.0", result) } - // 测试75%分位数 + // Testing 75% percentiles result, err = fn.Execute(ctx, []any{0.75, 1.0, 2.0, 3.0, 4.0}) if err != nil { t.Errorf("Execute error: %v", err) @@ -192,7 +192,7 @@ func TestCollectFunction(t *testing.T) { fn := NewCollectFunction() ctx := &FunctionContext{} - // 测试Execute方法 + // Test the Execute method result, err := fn.Execute(ctx, []any{"a", "b", "c"}) if err != nil { t.Errorf("Execute error: %v", err) @@ -202,7 +202,7 @@ func TestCollectFunction(t *testing.T) { t.Errorf("Execute collect result = %v, want %v", result, expected) } - // 测试聚合器方法 + // Test aggregator methods agg := fn.New().(*CollectFunction) agg.Add("x") agg.Add("y") @@ -213,14 +213,14 @@ func TestCollectFunction(t *testing.T) { t.Errorf("Agg collect result = %v, want %v", res, expected) } - // 测试Reset + // Test Reset agg.Reset() res = agg.Result().([]any) if len(res) != 0 { t.Errorf("Reset failed, result length = %d, want 0", len(res)) } - // 测试Clone + // Testing Clone clone := agg.Clone().(*CollectFunction) if !reflect.DeepEqual(clone.values, agg.values) { t.Errorf("Clone failed") @@ -231,7 +231,7 @@ func TestLastValueFunction(t *testing.T) { fn := NewLastValueFunction() ctx := &FunctionContext{} - // 测试Execute方法 + // Test the Execute method result, err := fn.Execute(ctx, []any{"a", "b", "c"}) if err != nil { t.Errorf("Execute error: %v", err) @@ -240,7 +240,7 @@ func TestLastValueFunction(t *testing.T) { t.Errorf("Execute last_value result = %v, want c", result) } - // 测试聚合器方法 + // Test aggregator methods agg := fn.New().(*LastValueFunction) agg.Add("x") agg.Add("y") @@ -250,14 +250,14 @@ func TestLastValueFunction(t *testing.T) { t.Errorf("Agg last_value result = %v, want z", res) } - // 测试Reset + // Test Reset agg.Reset() res = agg.Result() if res != nil { t.Errorf("Reset failed, result = %v, want nil", res) } - // 测试Clone + // Testing Clone clone := agg.Clone().(*LastValueFunction) if clone.lastValue != agg.lastValue { t.Errorf("Clone failed") @@ -268,7 +268,7 @@ func TestMergeAggFunction(t *testing.T) { fn := NewMergeAggFunction() ctx := &FunctionContext{} - // 测试Execute方法 + // Test the Execute method result, err := fn.Execute(ctx, []any{"a", "b", "c"}) if err != nil { t.Errorf("Execute error: %v", err) @@ -278,7 +278,7 @@ func TestMergeAggFunction(t *testing.T) { t.Errorf("Execute merge_agg result = %v, want %v", result, expected) } - // 测试聚合器方法 + // Test aggregator methods agg := fn.New().(*MergeAggFunction) agg.Add("x") agg.Add("y") @@ -289,14 +289,14 @@ func TestMergeAggFunction(t *testing.T) { t.Errorf("Agg merge_agg result = %v, want %v", res, expected) } - // 测试Reset + // Test Reset agg.Reset() res2 := agg.Result() if res2 != nil { t.Errorf("Reset failed, result = %v, want nil", res2) } - // 测试Clone + // Testing Clone clone := agg.Clone().(*MergeAggFunction) if !reflect.DeepEqual(clone.values, agg.values) { t.Errorf("Clone failed") @@ -304,7 +304,7 @@ func TestMergeAggFunction(t *testing.T) { } func TestAggregatorFunctions(t *testing.T) { - // 测试StdDevAggregatorFunction + // Test StdDevAggregatorFunction stdDevAgg := NewStdDevAggregatorFunction() agg := stdDevAgg.New().(*StdDevAggregatorFunction) agg.Add(1.0) @@ -324,7 +324,7 @@ func TestAggregatorFunctions(t *testing.T) { t.Errorf("StdDevAggregator Clone failed") } - // 测试MedianAggregatorFunction + // Test MedianAggregatorFunction medianAgg := NewMedianAggregatorFunction() agg2 := medianAgg.New().(*MedianAggregatorFunction) agg2.Add(1.0) @@ -343,7 +343,7 @@ func TestAggregatorFunctions(t *testing.T) { t.Errorf("MedianAggregator Clone failed") } - // 测试PercentileAggregatorFunction + // Test the PercentileAggregatorFunction percentileAgg := NewPercentileAggregatorFunction() agg3 := percentileAgg.New().(*PercentileAggregatorFunction) agg3.Add(1.0) @@ -363,7 +363,7 @@ func TestAggregatorFunctions(t *testing.T) { t.Errorf("PercentileAggregator Clone failed") } - // 测试CollectAggregatorFunction + // Test CollectAggregatorFunction collectAgg := NewCollectAggregatorFunction() agg4 := collectAgg.New().(*CollectAggregatorFunction) agg4.Add("a") @@ -383,7 +383,7 @@ func TestAggregatorFunctions(t *testing.T) { t.Errorf("CollectAggregator Clone failed") } - // 测试LastValueAggregatorFunction + // Test the LastValueAggregatorFunction lastValueAgg := NewLastValueAggregatorFunction() agg5 := lastValueAgg.New().(*LastValueAggregatorFunction) agg5.Add("a") @@ -402,7 +402,7 @@ func TestAggregatorFunctions(t *testing.T) { t.Errorf("LastValueAggregator Clone failed") } - // 测试MergeAggAggregatorFunction + // Test MergeAggAggregatorFunction mergeAggAgg := NewMergeAggAggregatorFunction() agg6 := mergeAggAgg.New().(*MergeAggAggregatorFunction) agg6.Add("a") @@ -422,7 +422,7 @@ func TestAggregatorFunctions(t *testing.T) { t.Errorf("MergeAggAggregator Clone failed") } - // 测试StdDevSAggregatorFunction + // Test StdDevSAggregatorFunction stdDevSAgg := NewStdDevSAggregatorFunction() agg7 := stdDevSAgg.New().(*StdDevSAggregatorFunction) agg7.Add(1.0) @@ -442,12 +442,12 @@ func TestAggregatorFunctions(t *testing.T) { t.Errorf("StdDevSAggregator Clone failed") } - // 测试DeduplicateAggregatorFunction + // Test DeduplicateAggregatorFunction dedupAgg := NewDeduplicateAggregatorFunction() agg8 := dedupAgg.New().(*DeduplicateAggregatorFunction) agg8.Add("a") agg8.Add("b") - agg8.Add("a") // 重复 + agg8.Add("a") // Repeat agg8.Add("c") res8 := agg8.Result().([]any) expected = []any{"a", "b", "c"} @@ -463,7 +463,7 @@ func TestAggregatorFunctions(t *testing.T) { t.Errorf("DeduplicateAggregator Clone failed") } - // 测试VarAggregatorFunction + // Test VarAggregatorFunction varAgg := NewVarAggregatorFunction() agg9 := varAgg.New().(*VarAggregatorFunction) agg9.Add(1.0) @@ -483,7 +483,7 @@ func TestAggregatorFunctions(t *testing.T) { t.Errorf("VarAggregator Clone failed") } - // 测试VarSAggregatorFunction + // Test the VarSAggregatorFunction varSAgg := NewVarSAggregatorFunction() agg10 := varSAgg.New().(*VarSAggregatorFunction) agg10.Add(1.0) @@ -505,12 +505,12 @@ func TestAggregatorFunctions(t *testing.T) { } func TestAggregatorEdgeCases(t *testing.T) { - // PercentileAggregatorFunction Validate边界 + // PercentileAggregatorFunction Validate boundary agg := NewPercentileAggregatorFunction() if err := agg.Validate([]any{1.0}); err == nil { t.Error("PercentileAggregatorFunction.Validate should fail for insufficient args") } - // 不再直接调用Execute避免panic + // No longer directly calling Execute to avoid panic // _, err := agg.Execute(nil, []any{}) // if err == nil { // t.Error("PercentileAggregatorFunction.Execute should fail for empty args") @@ -519,7 +519,7 @@ func TestAggregatorEdgeCases(t *testing.T) { agg2.Reset() _ = agg2.Clone() - // CollectAggregatorFunction Validate/Execute边界 + // CollectAggregatorFunction Validate/Execute boundary agg3 := NewCollectAggregatorFunction() if err := agg3.Validate([]any{}); err == nil { t.Error("CollectAggregatorFunction.Validate should fail for insufficient args") @@ -532,7 +532,7 @@ func TestAggregatorEdgeCases(t *testing.T) { agg4.Reset() _ = agg4.Clone() - // LastValueAggregatorFunction Validate/Execute边界 + // LastValueAggregatorFunction Validate/Execute boundary agg5 := NewLastValueAggregatorFunction() if err := agg5.Validate([]any{}); err == nil { t.Error("LastValueAggregatorFunction.Validate should fail for insufficient args") @@ -545,7 +545,7 @@ func TestAggregatorEdgeCases(t *testing.T) { agg6.Reset() _ = agg6.Clone() - // MergeAggAggregatorFunction Validate/Execute边界 + // MergeAggAggregatorFunction Validate/Execute boundary agg7 := NewMergeAggAggregatorFunction() if err := agg7.Validate([]any{}); err == nil { t.Error("MergeAggAggregatorFunction.Validate should fail for insufficient args") @@ -558,7 +558,7 @@ func TestAggregatorEdgeCases(t *testing.T) { agg8.Reset() _ = agg8.Clone() - // StdDevSAggregatorFunction Validate/Execute边界 + // StdDevSAggregatorFunction Validate/Execute boundary agg9 := NewStdDevSAggregatorFunction() if err := agg9.Validate([]any{}); err == nil { t.Error("StdDevSAggregatorFunction.Validate should fail for insufficient args") @@ -571,7 +571,7 @@ func TestAggregatorEdgeCases(t *testing.T) { agg10.Reset() _ = agg10.Clone() - // DeduplicateAggregatorFunction Validate/Execute边界 + // DeduplicateAggregatorFunction Validate/Execute boundary agg11 := NewDeduplicateAggregatorFunction() if err := agg11.Validate([]any{}); err == nil { t.Error("DeduplicateAggregatorFunction.Validate should fail for insufficient args") @@ -584,7 +584,7 @@ func TestAggregatorEdgeCases(t *testing.T) { agg12.Reset() _ = agg12.Clone() - // VarAggregatorFunction Validate/Execute边界 + // VarAggregatorFunction Validate/Execute boundary agg13 := NewVarAggregatorFunction() if err := agg13.Validate([]any{}); err == nil { t.Error("VarAggregatorFunction.Validate should fail for insufficient args") @@ -597,7 +597,7 @@ func TestAggregatorEdgeCases(t *testing.T) { agg14.Reset() _ = agg14.Clone() - // VarSAggregatorFunction Validate/Execute边界 + // VarSAggregatorFunction Validate/Execute boundary agg15 := NewVarSAggregatorFunction() if err := agg15.Validate([]any{}); err == nil { t.Error("VarSAggregatorFunction.Validate should fail for insufficient args") diff --git a/functions/functions_analytical.go b/functions/functions_analytical.go index 1b49bc3..83bcf4a 100644 --- a/functions/functions_analytical.go +++ b/functions/functions_analytical.go @@ -4,7 +4,7 @@ import ( "fmt" ) -// LagFunction LAG函数 - 返回当前行之前的第N行的值 +// LagFunction LAG function - Returns the value of the Nth row before the current line type LagFunction struct { *BaseFunction } @@ -31,12 +31,12 @@ func (f *LagFunction) Validate(args []any) error { return nil } -// Execute 标量路径禁用:分析函数需跨行状态,只能作为独立字段/OVER 由状态机求值。 +// Execute scalar path disabled: The analysis function needs to be cross-line and can only be used as an independent field/OVER evaluated by the state machine. func (f *LagFunction) Execute(ctx *FunctionContext, args []any) (any, error) { return nil, fmt.Errorf("analytic function %q must be used as a field or with OVER, not in a scalar expression", f.GetName()) } -// LatestFunction 最新值函数 - 返回指定列的最新值 +// LatestFunction Latest Value Function - Returns the latest value for the specified column type LatestFunction struct { *BaseFunction } @@ -55,7 +55,7 @@ func (f *LatestFunction) Execute(ctx *FunctionContext, args []any) (any, error) return nil, fmt.Errorf("analytic function %q must be used as a field or with OVER, not in a scalar expression", f.GetName()) } -// ChangedColFunction 变化列函数 - 返回发生变化的列名 +// ChangedColFunction - Returns the column name where the change occurred type ChangedColFunction struct { *BaseFunction } @@ -74,7 +74,7 @@ func (f *ChangedColFunction) Execute(ctx *FunctionContext, args []any) (any, err return nil, fmt.Errorf("analytic function %q must be used as a field or with OVER, not in a scalar expression", f.GetName()) } -// HadChangedFunction 是否变化函数 - 判断指定列的值是否发生变化 +// HadChangedFunction - Checks whether the value of a specified column has changed type HadChangedFunction struct { *BaseFunction } @@ -93,11 +93,11 @@ func (f *HadChangedFunction) Execute(ctx *FunctionContext, args []any) (any, err return nil, fmt.Errorf("analytic function %q must be used as a field or with OVER, not in a scalar expression", f.GetName()) } -// ===== 流式状态机实现(走直连路径逐条 Apply)===== -// 与 AggregatorFunction 的批量 Add/Result 不同,这里是跨事件、逐条 Apply 的状态机, -// 由 stream.AnalyticEngine 为每个 PARTITION 各持一份。 +// ===== Stream state machine implementation (applying each directly connected path one by one) ===== +// Unlike AggregatorFunction's batch Add/Result, here it is a state machine that applies across events and applies one by one, +// By stream.AnalyticEngine holds a copy for each PARTITION. -// lagState 维护最近 offset 个历史值,Apply 返回前 offset 个值(无则 default/nil)。 +// lagState maintains the most recent offset historical values; Apply returns the previous offset values (if none, default/nil). type lagState struct { history []any } @@ -119,7 +119,7 @@ func (s *lagState) Apply(args []any) any { def = args[2] hasDef = true } - // 第 4 参数 ignoreNull:nil 值跳过,不存入历史(默认 true,与主流分析函数一致) + // 4th parameter ignoreNull: nil value skipped, not stored in history (default true, consistent with mainstream analysis functions) ignoreNull := true if len(args) >= 4 { ignoreNull = AnalyticToBool(args[3]) @@ -141,10 +141,10 @@ func (s *lagState) Apply(args []any) any { func (s *lagState) Reset() { s.history = nil } -// NewState 实现 StatefulAnalytic。 +// NewState implements StatefulAnalytic. func (f *LagFunction) NewState() AnalyticState { return &lagState{} } -// latestState 维护最新非空值。 +// latestState maintains the latest non-null values. type latestState struct { latest any hasVal bool @@ -168,7 +168,7 @@ func (s *latestState) Reset() { s.latest = nil; s.hasVal = false } func (f *LatestFunction) NewState() AnalyticState { return &latestState{} } -// hadChangedState 维护各列上次值,Apply 返回是否有变化(首次视为变化。 +// hadChangedState maintains the previous values in each column, and Apply returns whether there has been a change (the first time is considered a change). type hadChangedState struct { prev []any first bool @@ -189,7 +189,7 @@ func (s *hadChangedState) Apply(args []any) any { return true } changed := false - // ignoreNull+nil:不触发变化,且保留旧基准(不把 nil 写入 prev)。 + // ignoreNull+nil: does not trigger changes and retains the old reference (nil is not written to prev). newPrev := make([]any, len(values)) for i, v := range values { if ignoreNull && v == nil { @@ -207,8 +207,8 @@ func (s *hadChangedState) Apply(args []any) any { return changed } -// ApplyNamed 按列名比较整行(had_changed '*' 用)。列增删/乱序均按名字判定, -// 不受位置影响。ignoreNull+nil 不触发变化且保留旧基准。 +// ApplyNamed compares entire rows by column name (had_changed '*'). Additions, deletions, and disordered order are all determined by name, +// Not affected by location. ignoreNull+nil does not trigger changes and retains the old benchmark. func (s *hadChangedState) ApplyNamed(ignoreNull bool, cols map[string]any) any { if !s.firstNamed { s.firstNamed = true @@ -221,7 +221,7 @@ func (s *hadChangedState) ApplyNamed(ignoreNull bool, cols map[string]any) any { return true } changed := false - // 新增/变化的列 + // Columns that have been added/changed for k, v := range cols { if ignoreNull && v == nil { continue @@ -230,7 +230,7 @@ func (s *hadChangedState) ApplyNamed(ignoreNull bool, cols map[string]any) any { changed = true } } - // 删除的列(旧基准有、本次无)也算变化 + // Deleted columns (old benchmarks existed, but not this time) also count as changes for k, pv := range s.prevNamed { if _, had := cols[k]; !had { if !(ignoreNull && pv == nil) { @@ -238,7 +238,7 @@ func (s *hadChangedState) ApplyNamed(ignoreNull bool, cols map[string]any) any { } } } - // 更新基准:ignoreNull+nil 保留旧值 + // Update benchmark: ignoreNull+nil retains the old value next := make(map[string]any, len(cols)) for k, v := range cols { if ignoreNull && v == nil { diff --git a/functions/functions_array.go b/functions/functions_array.go index 4a74c6c..20c8b36 100644 --- a/functions/functions_array.go +++ b/functions/functions_array.go @@ -117,7 +117,7 @@ func (f *ArrayRemoveFunction) Execute(ctx *FunctionContext, args []any) (any, er return nil, fmt.Errorf("array_remove requires array input") } - result := make([]any, 0) // 初始化为空切片而不是nil切片 + result := make([]any, 0) // Initialize as an empty slice instead of a nil slice for i := 0; i < v.Len(); i++ { elem := v.Index(i).Interface() if !reflect.DeepEqual(elem, value) { @@ -169,7 +169,7 @@ func (s *hashSafeSet) add(elem any) bool { return true } -// ArrayDistinctFunction 数组去重 +// ArrayDistinctFunction Array deduplication type ArrayDistinctFunction struct { *BaseFunction } @@ -204,7 +204,7 @@ func (f *ArrayDistinctFunction) Execute(ctx *FunctionContext, args []any) (any, return result, nil } -// ArrayIntersectFunction 数组交集 +// ArrayIntersectFunction array intersection type ArrayIntersectFunction struct { *BaseFunction } @@ -233,13 +233,13 @@ func (f *ArrayIntersectFunction) Execute(ctx *FunctionContext, args []any) (any, return nil, fmt.Errorf("array_intersect requires array input for second argument") } - // 创建第二个数组的元素集合 + // Create a second set of elements in the array set2 := newHashSafeSet() for i := 0; i < v2.Len(); i++ { set2.add(v2.Index(i).Interface()) } - // 找交集 + // Find a connection seen := newHashSafeSet() result := make([]any, 0) @@ -252,7 +252,7 @@ func (f *ArrayIntersectFunction) Execute(ctx *FunctionContext, args []any) (any, return result, nil } -// ArrayUnionFunction 数组并集 +// ArrayUnionFunction array union type ArrayUnionFunction struct { *BaseFunction } @@ -284,7 +284,7 @@ func (f *ArrayUnionFunction) Execute(ctx *FunctionContext, args []any) (any, err seen := newHashSafeSet() result := make([]any, 0) - // 添加第一个数组的元素 + // Add elements from the first array for i := 0; i < v1.Len(); i++ { elem := v1.Index(i).Interface() if seen.add(elem) { @@ -292,7 +292,7 @@ func (f *ArrayUnionFunction) Execute(ctx *FunctionContext, args []any) (any, err } } - // 添加第二个数组的元素 + // Add elements from the second array for i := 0; i < v2.Len(); i++ { elem := v2.Index(i).Interface() if seen.add(elem) { @@ -302,7 +302,7 @@ func (f *ArrayUnionFunction) Execute(ctx *FunctionContext, args []any) (any, err return result, nil } -// ArrayExceptFunction 数组差集 +// ArrayExceptFunction array difference type ArrayExceptFunction struct { *BaseFunction } @@ -331,13 +331,13 @@ func (f *ArrayExceptFunction) Execute(ctx *FunctionContext, args []any) (any, er return nil, fmt.Errorf("array_except requires array input for second argument") } - // 创建第二个数组的元素集合 + // Create a second set of elements in the array set2 := newHashSafeSet() for i := 0; i < v2.Len(); i++ { set2.add(v2.Index(i).Interface()) } - // 找差集 + // Finding the difference seen := newHashSafeSet() result := make([]any, 0) diff --git a/functions/functions_array_test.go b/functions/functions_array_test.go index a042bd6..fb5cdd5 100644 --- a/functions/functions_array_test.go +++ b/functions/functions_array_test.go @@ -5,7 +5,7 @@ import ( "testing" ) -// 测试数组函数 +// Test array functions func TestArrayFunctions(t *testing.T) { tests := []struct { name string @@ -264,7 +264,7 @@ func TestArrayFunctionsUnhashable(t *testing.T) { } } -// TestArrayFunctionErrors 测试数组函数的错误处理 +// TestArrayFunctionErrors tests error handling of array functions func TestArrayFunctionErrors(t *testing.T) { tests := []struct { name string @@ -272,39 +272,39 @@ func TestArrayFunctionErrors(t *testing.T) { args []any wantErr bool }{ - // array_length 错误测试 + // array_length Error testing {"array_length nil", "array_length", []any{nil}, true}, {"array_length invalid type", "array_length", []any{"not an array"}, true}, - // array_contains 错误测试 + // array_contains Error testing {"array_contains nil array", "array_contains", []any{nil, 1}, true}, {"array_contains invalid type", "array_contains", []any{"not an array", 1}, true}, - // array_position 错误测试 + // array_position Error testing {"array_position nil array", "array_position", []any{nil, 1}, true}, {"array_position invalid type", "array_position", []any{"not an array", 1}, true}, - // array_remove 错误测试 + // array_remove Error testing {"array_remove nil array", "array_remove", []any{nil, 1}, true}, {"array_remove invalid type", "array_remove", []any{"not an array", 1}, true}, - // array_distinct 错误测试 + // array_distinct Error testing {"array_distinct nil", "array_distinct", []any{nil}, true}, {"array_distinct invalid type", "array_distinct", []any{"not an array"}, true}, - // array_intersect 错误测试 + // array_intersect Error testing {"array_intersect first nil", "array_intersect", []any{nil, []any{1, 2}}, true}, {"array_intersect second nil", "array_intersect", []any{[]any{1, 2}, nil}, true}, {"array_intersect first invalid type", "array_intersect", []any{"not an array", []any{1, 2}}, true}, {"array_intersect second invalid type", "array_intersect", []any{[]any{1, 2}, "not an array"}, true}, - // array_union 错误测试 + // array_union Error testing {"array_union first nil", "array_union", []any{nil, []any{1, 2}}, true}, {"array_union second nil", "array_union", []any{[]any{1, 2}, nil}, true}, {"array_union first invalid type", "array_union", []any{"not an array", []any{1, 2}}, true}, {"array_union second invalid type", "array_union", []any{[]any{1, 2}, "not an array"}, true}, - // array_except 错误测试 + // array_except Error testing {"array_except first nil", "array_except", []any{nil, []any{1, 2}}, true}, {"array_except second nil", "array_except", []any{[]any{1, 2}, nil}, true}, {"array_except first invalid type", "array_except", []any{"not an array", []any{1, 2}}, true}, diff --git a/functions/functions_conditional.go b/functions/functions_conditional.go index 1b903e2..a14c8ee 100644 --- a/functions/functions_conditional.go +++ b/functions/functions_conditional.go @@ -24,10 +24,10 @@ func (f *IfNullFunction) Validate(args []any) error { func (f *IfNullFunction) Execute(ctx *FunctionContext, args []any) (any, error) { if args[0] == nil { - // 当第一个参数为nil时,返回第二个参数 - // 如果第二个参数是数字0,确保返回float64类型以保持一致性 + // When the first argument is nil, the second parameter is returned + // If the second parameter is 0, make sure to return float64 type to maintain consistency if args[1] != nil { - // 尝试转换为float64以保持数值类型一致性 + // Try converting to float64 to maintain consistency between numeric types if val, ok := args[1].(int); ok && val == 0 { return 0.0, nil } @@ -116,7 +116,7 @@ func (f *GreatestFunction) Execute(ctx *FunctionContext, args []any) (any, error return nil, nil } - // 尝试转换为数字进行比较 + // Try to convert them into numbers for comparison maxVal, err1 := cast.ToFloat64E(max) currVal, err2 := cast.ToFloat64E(args[i]) @@ -125,7 +125,7 @@ func (f *GreatestFunction) Execute(ctx *FunctionContext, args []any) (any, error max = args[i] } } else { - // 如果不能转换为数字,则按字符串比较 + // If it cannot be converted to a number, it is compared by string maxStr := fmt.Sprintf("%v", max) currStr := fmt.Sprintf("%v", args[i]) if currStr > maxStr { @@ -136,7 +136,7 @@ func (f *GreatestFunction) Execute(ctx *FunctionContext, args []any) (any, error return max, nil } -// LeastFunction 返回最小值 +// LeastFunction returns the minimum value type LeastFunction struct { *BaseFunction } @@ -166,7 +166,7 @@ func (f *LeastFunction) Execute(ctx *FunctionContext, args []any) (any, error) { return nil, nil } - // 尝试转换为数字进行比较 + // Try to convert them into numbers for comparison minVal, err1 := cast.ToFloat64E(min) currVal, err2 := cast.ToFloat64E(args[i]) @@ -175,7 +175,7 @@ func (f *LeastFunction) Execute(ctx *FunctionContext, args []any) (any, error) { min = args[i] } } else { - // 如果不能转换为数字,则按字符串比较 + // If it cannot be converted to a number, it is compared by string minStr := fmt.Sprintf("%v", min) currStr := fmt.Sprintf("%v", args[i]) if currStr < minStr { @@ -186,7 +186,7 @@ func (f *LeastFunction) Execute(ctx *FunctionContext, args []any) (any, error) { return min, nil } -// CaseWhenFunction CASE WHEN表达式 +// CaseWhenFunction CASE WHEN expression type CaseWhenFunction struct { *BaseFunction } @@ -202,16 +202,16 @@ func (f *CaseWhenFunction) Validate(args []any) error { return fmt.Errorf("case_when requires at least 2 arguments") } - // 参数必须是偶数个(条件-值对)或奇数个(最后一个是默认值) + // Parameters must be an even number (condition-value pair) or an odd number (the last one is the default) if len(args)%2 == 0 { - // 偶数个参数,必须都是条件-值对 + // Even parameters must all be conditional-value pairs for i := 0; i < len(args); i += 2 { - // 条件应该是布尔值或可以转换为布尔值的表达式 + // The condition should be a boolean value or an expression that can be converted to a boolean value } } else { - // 奇数个参数,最后一个是默认值 + // Odd parameters, with the last one being the default value for i := 0; i < len(args)-1; i += 2 { - // 条件应该是布尔值或可以转换为布尔值的表达式 + // The condition should be a boolean value or an expression that can be converted to a boolean value } } @@ -223,15 +223,15 @@ func (f *CaseWhenFunction) Execute(ctx *FunctionContext, args []any) (any, error return nil, err } - // 处理条件-值对 + // Handle condition-value pairs for i := 0; i < len(args)-1; i += 2 { condition := args[i] value := args[i+1] - // 将条件转换为布尔值 + // Convert the condition into a boolean value condBool, err := cast.ToBoolE(condition) if err != nil { - // 如果无法转换为布尔值,检查是否为非零/非空值 + // If it cannot be converted to a boolean value, check whether it is non-zero/non-null if condition == nil { condBool = false } else { @@ -255,11 +255,11 @@ func (f *CaseWhenFunction) Execute(ctx *FunctionContext, args []any) (any, error } } - // 如果没有条件匹配,返回默认值(如果有) + // If no conditional match exists, returns the default value (if any) if len(args)%2 == 1 { return args[len(args)-1], nil } - // 没有默认值,返回 nil + // No default value, returns nil return nil, nil } diff --git a/functions/functions_conditional_test.go b/functions/functions_conditional_test.go index 1aaed8a..7756423 100644 --- a/functions/functions_conditional_test.go +++ b/functions/functions_conditional_test.go @@ -4,7 +4,7 @@ import ( "testing" ) -// TestConditionalFunctions 测试条件函数的基本功能 +// TestConditionalFunctions Tests the basic functions of conditional functions func TestConditionalFunctions(t *testing.T) { tests := []struct { name string @@ -50,7 +50,7 @@ func TestConditionalFunctions(t *testing.T) { expected: 1, }, - // case_when 函数测试 + // case_when Function testing { name: "case_when simple", funcName: "case_when", @@ -104,7 +104,7 @@ func TestConditionalFunctions(t *testing.T) { } } -// TestConditionalFunctionValidation 测试条件函数的参数验证 +// TestConditionalFunctionValidation: Parameter validation of the test conditional function func TestConditionalFunctionValidation(t *testing.T) { tests := []struct { name string @@ -214,7 +214,7 @@ func TestConditionalFunctionValidation(t *testing.T) { } } -// TestConditionalFunctionEdgeCases 测试条件函数的边界情况 +// TestConditionalFunctionEdgeCases tests the boundary status of the condition function func TestConditionalFunctionEdgeCases(t *testing.T) { tests := []struct { name string diff --git a/functions/functions_conversion.go b/functions/functions_conversion.go index e402a73..40195ca 100644 --- a/functions/functions_conversion.go +++ b/functions/functions_conversion.go @@ -113,7 +113,7 @@ func (f *Dec2HexFunction) Execute(ctx *FunctionContext, args []any) (any, error) return fmt.Sprintf("%x", val), nil } -// EncodeFunction 将输入值编码为指定格式的字符串 +// EncodeFunction encodes the input value into a string in the specified format type EncodeFunction struct { *BaseFunction } @@ -170,7 +170,7 @@ func (f *EncodeFunction) Execute(ctx *FunctionContext, args []any) (any, error) } } -// DecodeFunction 将编码的字符串解码为原始数据 +// DecodeFunction decodes the encoded string back to its original data type DecodeFunction struct { *BaseFunction } @@ -232,7 +232,7 @@ func (f *DecodeFunction) Execute(ctx *FunctionContext, args []any) (any, error) } } -// ConvertTzFunction 时区转换函数 +// ConvertTzFunction is a time zone conversion function type ConvertTzFunction struct { *BaseFunction } @@ -248,14 +248,14 @@ func (f *ConvertTzFunction) Validate(args []any) error { } func (f *ConvertTzFunction) Execute(ctx *FunctionContext, args []any) (any, error) { - // 获取时间值 + // Gain time values var t time.Time switch v := args[0].(type) { case time.Time: t = v case string: var err error - // 尝试多种时间格式解析 + // Try parsing multiple time formats formats := []string{ time.RFC3339, "2006-01-02 15:04:05", @@ -275,23 +275,23 @@ func (f *ConvertTzFunction) Execute(ctx *FunctionContext, args []any) (any, erro return nil, fmt.Errorf("time value must be time.Time or string") } - // 获取目标时区 + // Obtain the target time zone timezone, err := cast.ToStringE(args[1]) if err != nil { return nil, err } - // 加载时区 + // Loading time zone loc, err := time.LoadLocation(timezone) if err != nil { return nil, fmt.Errorf("invalid timezone: %s", timezone) } - // 转换时区 + // Switching time zones return t.In(loc), nil } -// ToSecondsFunction 转换为Unix时间戳(秒) +// ToSecondsFunction to convert Unix timestamps (seconds) type ToSecondsFunction struct { *BaseFunction } @@ -307,14 +307,14 @@ func (f *ToSecondsFunction) Validate(args []any) error { } func (f *ToSecondsFunction) Execute(ctx *FunctionContext, args []any) (any, error) { - // 获取时间值 + // Gain time values var t time.Time switch v := args[0].(type) { case time.Time: t = v case string: var err error - // 尝试多种时间格式解析 + // Try parsing multiple time formats formats := []string{ time.RFC3339, "2006-01-02 15:04:05", @@ -331,7 +331,7 @@ func (f *ToSecondsFunction) Execute(ctx *FunctionContext, args []any) (any, erro return nil, fmt.Errorf("invalid time format: %s", v) } case int, int32, int64, float32, float64: - // 数值按 Unix 秒处理 + // Values are processed in Unix seconds sec, err := cast.ToInt64E(v) if err != nil { return nil, fmt.Errorf("invalid timestamp: %v", err) @@ -344,7 +344,7 @@ func (f *ToSecondsFunction) Execute(ctx *FunctionContext, args []any) (any, erro return t.Unix(), nil } -// ChrFunction 返回对应ASCII字符 +// ChrFunction returns the corresponding ASCII character type ChrFunction struct { *BaseFunction } @@ -372,7 +372,7 @@ func (f *ChrFunction) Execute(ctx *FunctionContext, args []any) (any, error) { return string(rune(code)), nil } -// UrlEncodeFunction URL编码函数 +// UrlEncodeFunction URL encoding function type UrlEncodeFunction struct { *BaseFunction } @@ -400,7 +400,7 @@ func (f *UrlEncodeFunction) Execute(ctx *FunctionContext, args []any) (any, erro return url.QueryEscape(input), nil } -// UrlDecodeFunction URL解码函数 +// UrlDecodeFunction URL decoder function type UrlDecodeFunction struct { *BaseFunction } @@ -432,47 +432,47 @@ func (f *UrlDecodeFunction) Execute(ctx *FunctionContext, args []any) (any, erro return result, nil } -// TruncFunction 截断小数位数 +// TruncFunction Truncates decimal places type TruncFunction struct { *BaseFunction } -// NewTruncFunction 创建新的 trunc 函数 +// NewTruncFunction creates a new trunc function func NewTruncFunction() *TruncFunction { return &TruncFunction{ BaseFunction: NewBaseFunction("trunc", TypeConversion, "转换函数", "截断小数位数", 2, 2), } } -// Validate 验证参数 +// Validate the parameters func (f *TruncFunction) Validate(args []any) error { return f.ValidateArgCount(args) } -// Execute 执行函数 +// Execute the function func (f *TruncFunction) Execute(ctx *FunctionContext, args []any) (any, error) { if err := f.Validate(args); err != nil { return nil, err } - // 转换第一个参数为浮点数(NULL/非数值返回错误,不再静默当 0) + // Convert the first parameter to a floating-point number (NULL/non-numeric return error, no longer silenced when 0) num, err := cast.ToFloat64E(args[0]) if err != nil { return nil, err } - // 转换第二个参数为整数(精度) + // Convert the second parameter to an integer (precision) precision, err := cast.ToIntE(args[1]) if err != nil { return nil, err } - // 精度不能为负数 + // Accuracy cannot be negative if precision < 0 { return nil, fmt.Errorf("trunc precision cannot be negative") } - // 计算截断。precision 过大时 multiplier 溢出为 +Inf,截断无意义,报错。 + // Calculation truncation. If precision is too large, the multiplier overflows to +Inf, making truncation meaningless and causing errors. multiplier := math.Pow(10, float64(precision)) if math.IsInf(multiplier, 0) { return nil, fmt.Errorf("trunc precision %d too large", precision) diff --git a/functions/functions_conversion_test.go b/functions/functions_conversion_test.go index e86b6bf..36fb44a 100644 --- a/functions/functions_conversion_test.go +++ b/functions/functions_conversion_test.go @@ -13,7 +13,7 @@ func TestNewConversionFunctions(t *testing.T) { want any wantErr bool }{ - // convert_tz 函数测试 + // convert_tz Function Testing { name: "convert_tz with time.Time", funcName: "convert_tz", @@ -40,7 +40,7 @@ func TestNewConversionFunctions(t *testing.T) { wantErr: true, }, - // to_seconds 函数测试 + // to_seconds Function testing { name: "to_seconds with time.Time", funcName: "to_seconds", @@ -69,7 +69,7 @@ func TestNewConversionFunctions(t *testing.T) { want: int64(1672531200), wantErr: false, }, - // chr 函数测试 + // chr function test { name: "chr valid ASCII code", funcName: "chr", @@ -97,7 +97,7 @@ func TestNewConversionFunctions(t *testing.T) { wantErr: true, }, - // trunc 函数测试 + // trunc function test { name: "trunc positive number", funcName: "trunc", @@ -106,7 +106,7 @@ func TestNewConversionFunctions(t *testing.T) { wantErr: false, }, - // url_encode 函数测试 + // url_encode Function testing { name: "url_encode basic", funcName: "url_encode", @@ -135,7 +135,7 @@ func TestNewConversionFunctions(t *testing.T) { wantErr: true, }, - // url_decode 函数测试 + // url_decode Function testing { name: "url_decode basic", funcName: "url_decode", @@ -223,16 +223,16 @@ func TestNewConversionFunctions(t *testing.T) { } if !tt.wantErr { - // 对于时间类型,需要特殊处理比较 + // For time types, special treatment and comparison are needed if tt.funcName == "convert_tz" { if resultTime, ok := result.(time.Time); ok { if wantTime, ok := tt.want.(time.Time); ok { - // 比较时间戳而不是直接比较时间对象 + // Compare timestamps rather than directly comparing time objects if resultTime.Unix() != wantTime.Unix() { t.Errorf("Execute() = %v, want %v", result, tt.want) } } else { - // 如果期望值不是时间类型,只检查结果是否为时间类型 + // If the expected value is not of the time type, only check whether the result is of the time type if resultTime.IsZero() { t.Errorf("Execute() returned zero time") } diff --git a/functions/functions_datetime.go b/functions/functions_datetime.go index a3a58a0..e514744 100644 --- a/functions/functions_datetime.go +++ b/functions/functions_datetime.go @@ -22,9 +22,9 @@ func durationFromInterval(interval int64, unit time.Duration) (time.Duration, er return time.Duration(interval) * unit, nil } -// dateArgString 把日期参数转为可被 time.Parse 的字符串。time.Time 入参(如 now()) -// 按 "2006-01-02 15:04:05" 格式化,避免 time.Time.String() 的时区/纳秒文本导致解析失败; -// 其余类型走 cast.ToStringE。供 year/month/date_add 等日期函数统一使用。 +// dateArgString Convert the date parameter into a string that can be time.Parse. time.Time Entry (e.g., now()) +// Format according to "2006-01-02 15:04:05" to avoid time.Time.String() in the timezone/nanosecond text causes parsing failure; +// The rest of the types go cast.ToStringE. Unified use for date functions such as year/month/date_add. func dateArgString(v any) (string, error) { if t, ok := v.(time.Time); ok { return t.Format("2006-01-02 15:04:05"), nil @@ -124,7 +124,7 @@ func (f *DateAddFunction) Execute(ctx *FunctionContext, args []any) (any, error) t, err := time.Parse("2006-01-02 15:04:05", dateStr) if err != nil { - // 尝试其他格式 + // Try other formats if t, err = time.Parse("2006-01-02", dateStr); err != nil { return nil, fmt.Errorf("invalid date format: %v", err) } @@ -162,7 +162,7 @@ func (f *DateAddFunction) Execute(ctx *FunctionContext, args []any) (any, error) return t.Format("2006-01-02 15:04:05"), nil } -// DateSubFunction 日期减法函数 +// DateSubFunction is a date subtraction function type DateSubFunction struct { *BaseFunction } @@ -232,7 +232,7 @@ func (f *DateSubFunction) Execute(ctx *FunctionContext, args []any) (any, error) return t.Format("2006-01-02 15:04:05"), nil } -// DateDiffFunction 日期差函数 +// DateDiffFunction type DateDiffFunction struct { *BaseFunction } @@ -297,7 +297,7 @@ func (f *DateDiffFunction) Execute(ctx *FunctionContext, args []any) (any, error } } -// DateFormatFunction 日期格式化函数 +// DateFormatFunction Date formatting function type DateFormatFunction struct { *BaseFunction } @@ -330,14 +330,14 @@ func (f *DateFormatFunction) Execute(ctx *FunctionContext, args []any) (any, err } } - // 转换常见的格式字符串 + // Convert common format strings goFormat := convertToGoFormat(format) return t.Format(goFormat), nil } -// convertToGoFormat 将常见的日期格式转换为Go的时间格式 +// convertToGoFormat converts common date formats into Go time formats func convertToGoFormat(format string) string { - // 按照长度从长到短的顺序替换,避免短的模式覆盖长的模式 + // Replace them in order from length to shortest, avoiding short patterns overriding long patterns replacements := []struct { old string new string @@ -365,7 +365,7 @@ func convertToGoFormat(format string) string { return result } -// DateParseFunction 日期解析函数 +// DateParseFunction Date parsing function type DateParseFunction struct { *BaseFunction } @@ -400,7 +400,7 @@ func (f *DateParseFunction) Execute(ctx *FunctionContext, args []any) (any, erro return t.Format("2006-01-02 15:04:05"), nil } -// ExtractFunction 提取日期部分函数 +// ExtractFunction Extract the date part function type ExtractFunction struct { *BaseFunction } @@ -455,7 +455,7 @@ func (f *ExtractFunction) Execute(ctx *FunctionContext, args []any) (any, error) } } -// UnixTimestampFunction Unix时间戳函数 +// UnixTimestampFunction: Unix timestamp function type UnixTimestampFunction struct { *BaseFunction } @@ -471,7 +471,7 @@ func (f *UnixTimestampFunction) Validate(args []any) error { } func (f *UnixTimestampFunction) Execute(ctx *FunctionContext, args []any) (any, error) { - // 无参返回当前 Unix 秒 + // Returns the current Unix second without parameters if len(args) == 0 { return time.Now().Unix(), nil } @@ -498,7 +498,7 @@ func (f *UnixTimestampFunction) Execute(ctx *FunctionContext, args []any) (any, } } -// FromUnixtimeFunction 从Unix时间戳转换函数 +// FromUnixtimeFunction: A function that converts Unix timestamps type FromUnixtimeFunction struct { *BaseFunction } @@ -523,7 +523,7 @@ func (f *FromUnixtimeFunction) Execute(ctx *FunctionContext, args []any) (any, e return t.Format("2006-01-02 15:04:05"), nil } -// YearFunction 提取年份函数 +// YearFunction extracts the year function type YearFunction struct { *BaseFunction } @@ -539,7 +539,7 @@ func (f *YearFunction) Validate(args []any) error { } func (f *YearFunction) Execute(ctx *FunctionContext, args []any) (any, error) { - // 尝试转换为字符串并解析 + // Try converting to strings and parsing dateStr, err := dateArgString(args[0]) if err != nil { return nil, fmt.Errorf("invalid date: %v", err) @@ -555,7 +555,7 @@ func (f *YearFunction) Execute(ctx *FunctionContext, args []any) (any, error) { return t.Year(), nil } -// MonthFunction 提取月份函数 +// MonthFunction extracts the month function type MonthFunction struct { *BaseFunction } @@ -571,7 +571,7 @@ func (f *MonthFunction) Validate(args []any) error { } func (f *MonthFunction) Execute(ctx *FunctionContext, args []any) (any, error) { - // 转换为字符串并解析 + // Convert to strings and parse dateStr, err := dateArgString(args[0]) if err != nil { return nil, fmt.Errorf("invalid date: %v", err) @@ -587,7 +587,7 @@ func (f *MonthFunction) Execute(ctx *FunctionContext, args []any) (any, error) { return int(t.Month()), nil } -// DayFunction 提取日期函数 +// DayFunction extracts the date function type DayFunction struct { *BaseFunction } @@ -618,7 +618,7 @@ func (f *DayFunction) Execute(ctx *FunctionContext, args []any) (any, error) { return t.Day(), nil } -// HourFunction 提取小时函数 +// HourFunction extracts hourly functions type HourFunction struct { *BaseFunction } @@ -649,7 +649,7 @@ func (f *HourFunction) Execute(ctx *FunctionContext, args []any) (any, error) { return t.Hour(), nil } -// MinuteFunction 提取分钟函数 +// MinuteFunction extracts the minute function type MinuteFunction struct { *BaseFunction } @@ -680,7 +680,7 @@ func (f *MinuteFunction) Execute(ctx *FunctionContext, args []any) (any, error) return t.Minute(), nil } -// SecondFunction 提取秒数函数 +// SecondFunction extracts the seconds function type SecondFunction struct { *BaseFunction } @@ -711,7 +711,7 @@ func (f *SecondFunction) Execute(ctx *FunctionContext, args []any) (any, error) return t.Second(), nil } -// DayOfWeekFunction 获取星期几函数 +// DayOfWeekFunction to get the day of the week type DayOfWeekFunction struct { *BaseFunction } @@ -742,7 +742,7 @@ func (f *DayOfWeekFunction) Execute(ctx *FunctionContext, args []any) (any, erro return int(t.Weekday()), nil } -// DayOfYearFunction 获取一年中的第几天函数 +// DayOfYearFunction Gets the day of the year type DayOfYearFunction struct { *BaseFunction } @@ -773,7 +773,7 @@ func (f *DayOfYearFunction) Execute(ctx *FunctionContext, args []any) (any, erro return t.YearDay(), nil } -// WeekOfYearFunction 获取一年中的第几周函数 +// WeekOfYearFunction gets the week of the year type WeekOfYearFunction struct { *BaseFunction } diff --git a/functions/functions_datetime_test.go b/functions/functions_datetime_test.go index 7bb8543..b0581d7 100644 --- a/functions/functions_datetime_test.go +++ b/functions/functions_datetime_test.go @@ -6,8 +6,8 @@ import ( "time" ) -// now() 返回 time.Time;日期函数必须能直接吃 time.Time,否则 SELECT 字段静默变 nil、 -// WHERE 静默丢全部行。以 Go time 方法作为期望值 oracle。 +// now() returns time.Time; The date function must be able to directly eat time. Time, otherwise the SELECT field will mute to nil, +// WHERE silent throws all the lines. Use the Go time method as the expected oracle. func TestDateTimeFunctions_TimeTimeInput(t *testing.T) { tm := time.Date(2025, 8, 25, 15, 30, 45, 0, time.UTC) _, isoWeek := tm.ISOWeek() @@ -53,7 +53,7 @@ func TestDateTimeFunctions(t *testing.T) { expected any wantErr bool }{ - // DateFormatFunction 测试 + // DateFormatFunction test { name: "date_format basic", function: NewDateFormatFunction(), @@ -68,7 +68,7 @@ func TestDateTimeFunctions(t *testing.T) { expected: "2025/08/25", wantErr: false, }, - // DateAddFunction 测试 + // DateAddFunction test { name: "date_add years", function: NewDateAddFunction(), @@ -125,7 +125,7 @@ func TestDateTimeFunctions(t *testing.T) { expected: nil, wantErr: true, }, - // DateSubFunction 测试 + // DateSubFunction test { name: "date_sub years", function: NewDateSubFunction(), @@ -182,7 +182,7 @@ func TestDateTimeFunctions(t *testing.T) { expected: nil, wantErr: true, }, - // DateDiffFunction 测试 + // DateDiffFunction test { name: "date_diff years", function: NewDateDiffFunction(), @@ -246,7 +246,7 @@ func TestDateTimeFunctions(t *testing.T) { expected: nil, wantErr: true, }, - // YearFunction 测试 + // YearFunction test { name: "year extraction", function: NewYearFunction(), @@ -261,7 +261,7 @@ func TestDateTimeFunctions(t *testing.T) { expected: 2025, wantErr: false, }, - // MonthFunction 测试 + // MonthFunction test { name: "month extraction", function: NewMonthFunction(), @@ -283,7 +283,7 @@ func TestDateTimeFunctions(t *testing.T) { expected: nil, wantErr: true, }, - // DayFunction 测试 + // DayFunction test { name: "day extraction", function: NewDayFunction(), @@ -305,7 +305,7 @@ func TestDateTimeFunctions(t *testing.T) { expected: nil, wantErr: true, }, - // HourFunction 测试 + // HourFunction test { name: "hour extraction", function: NewHourFunction(), @@ -327,7 +327,7 @@ func TestDateTimeFunctions(t *testing.T) { expected: nil, wantErr: true, }, - // MinuteFunction 测试 + // MinuteFunction test { name: "minute extraction", function: NewMinuteFunction(), @@ -349,7 +349,7 @@ func TestDateTimeFunctions(t *testing.T) { expected: nil, wantErr: true, }, - // SecondFunction 测试 + // SecondFunction test { name: "second extraction", function: NewSecondFunction(), @@ -371,7 +371,7 @@ func TestDateTimeFunctions(t *testing.T) { expected: nil, wantErr: true, }, - // UnixTimestampFunction 测试 + // UnixTimestampFunction test { name: "unix_timestamp", function: NewUnixTimestampFunction(), @@ -407,7 +407,7 @@ func TestDateTimeFunctions(t *testing.T) { expected: int64(1672531200), wantErr: false, }, - // FromUnixtimeFunction 测试 + // FromUnixtimeFunction test { name: "from_unixtime", function: NewFromUnixtimeFunction(), @@ -422,7 +422,7 @@ func TestDateTimeFunctions(t *testing.T) { expected: nil, wantErr: true, }, - // ExtractFunction 测试 + // ExtractFunction test { name: "extract year", function: NewExtractFunction(), @@ -486,7 +486,7 @@ func TestDateTimeFunctions(t *testing.T) { expected: 0, wantErr: true, }, - // DayOfWeekFunction 测试 + // DayOfWeekFunction test { name: "dayofweek", function: NewDayOfWeekFunction(), @@ -501,7 +501,7 @@ func TestDateTimeFunctions(t *testing.T) { expected: 1, // Monday wantErr: false, }, - // DayOfYearFunction 测试 + // DayOfYearFunction test { name: "dayofyear", function: NewDayOfYearFunction(), @@ -516,7 +516,7 @@ func TestDateTimeFunctions(t *testing.T) { expected: 237, wantErr: false, }, - // WeekOfYearFunction 测试 + // WeekOfYearFunction test { name: "weekofyear", function: NewWeekOfYearFunction(), @@ -531,7 +531,7 @@ func TestDateTimeFunctions(t *testing.T) { expected: 35, wantErr: false, }, - // DateParseFunction 测试 + // DateParseFunction test { name: "date_parse", function: NewDateParseFunction(), @@ -550,7 +550,7 @@ func TestDateTimeFunctions(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - // 验证参数 + // Validate parameters if err := tt.function.Validate(tt.args); err != nil { if !tt.wantErr { t.Errorf("Validate() error = %v, wantErr %v", err, tt.wantErr) @@ -558,7 +558,7 @@ func TestDateTimeFunctions(t *testing.T) { return } - // 执行函数 + // Execute the function result, err := tt.function.Execute(nil, tt.args) if (err != nil) != tt.wantErr { t.Errorf("Execute() error = %v, wantErr %v", err, tt.wantErr) @@ -572,7 +572,7 @@ func TestDateTimeFunctions(t *testing.T) { } } -// TestUnixTimestampNoArgs 校验 unix_timestamp() 无参返回当前 Unix 秒 +// TestUnixTimestampNoArgs check unix_timestamp() returns the current Unix second without parameters func TestUnixTimestampNoArgs(t *testing.T) { f := NewUnixTimestampFunction() if err := f.Validate([]any{}); err != nil { @@ -592,7 +592,7 @@ func TestUnixTimestampNoArgs(t *testing.T) { } } -// TestDateTimeFunctionValidation 测试日期时间函数的参数验证 +// TestDateTimeFunctionValidation: Parameter validation of the test date-time function func TestDateTimeFunctionValidation(t *testing.T) { tests := []struct { name string @@ -637,7 +637,7 @@ func TestDateTimeFunctionValidation(t *testing.T) { } func TestDateTimeRegistration(t *testing.T) { - // 测试函数是否正确注册 + // Test whether the function is registered correctly dateTimeFunctions := []string{ "date_format", "date_add", diff --git a/functions/functions_expr.go b/functions/functions_expr.go index aeaf3a6..2b57c0a 100644 --- a/functions/functions_expr.go +++ b/functions/functions_expr.go @@ -6,7 +6,7 @@ import ( "github.com/rulego/streamsql/utils/cast" ) -// ExprFunction expr函数,用于在SQL中执行表达式 +// ExprFunction is an expr function used to execute expressions in SQL type ExprFunction struct { *BaseFunction } @@ -26,13 +26,13 @@ func (f *ExprFunction) Execute(ctx *FunctionContext, args []any) (any, error) { return nil, fmt.Errorf("expr function requires exactly 1 argument") } - // 获取表达式字符串 + // Get the expression string expressionStr, err := cast.ToStringE(args[0]) if err != nil { return nil, fmt.Errorf("expr function argument must be a string: %v", err) } - // 使用 ExprBridge 执行表达式 + // Executing expressions using ExprBridge bridge := GetExprBridge() result, err := bridge.EvaluateExpression(expressionStr, ctx.Data) if err != nil { diff --git a/functions/functions_expr_test.go b/functions/functions_expr_test.go index 62fa3fd..9d3e96a 100644 --- a/functions/functions_expr_test.go +++ b/functions/functions_expr_test.go @@ -13,21 +13,21 @@ func TestExprFunction(t *testing.T) { }, } - // 测试Execute方法 + // Test the Execute method _, err := fn.Execute(ctx, []any{"x + y"}) if err != nil { t.Errorf("Execute error: %v", err) } - // 注意:这里的结果取决于表达式求值器的实现 - // 我们主要测试函数调用是否成功 + // Note: The result here depends on the implementation of the expression evaluator + // We mainly test whether the function call is successful - // 测试Validate方法 + // Test the Validate method err = fn.Validate([]any{"test"}) if err != nil { t.Errorf("Validate error: %v", err) } - // 测试参数数量验证 + // Verification of test parameter quantities err = fn.Validate([]any{}) if err == nil { t.Errorf("Validate should fail for empty args") @@ -41,34 +41,34 @@ func TestExprFunction(t *testing.T) { func TestExprFunctionEdgeCases(t *testing.T) { fn := NewExprFunction() - // Validate参数数量不符 + // The number of validate parameters does not match if err := fn.Validate([]any{}); err == nil { t.Error("ExprFunction.Validate should fail for empty args") } if err := fn.Validate([]any{"a", "b"}); err == nil { t.Error("ExprFunction.Validate should fail for too many args") } - // Execute空参数 + // Execute empty parameters _, err := fn.Execute(nil, []any{}) if err == nil { t.Error("ExprFunction.Execute should fail for empty args") } - // 测试非字符串参数(现在应该成功) + // Testing non-string parameters (should now succeed) ctx := &FunctionContext{Data: map[string]any{}} _, err = fn.Execute(ctx, []any{123}) if err != nil { t.Errorf("ExprFunction.Execute should accept non-string argument: %v", err) } - // 测试无效表达式 + // Test for invalid expressions _, err = fn.Execute(ctx, []any{"invalid expression +++"}) if err == nil { t.Error("ExprFunction.Execute should fail for invalid expression") } } -// TestExprFunctionCreation 测试ExprFunction的创建和属性 +// TestExprFunctionCreation tests the creation and properties of ExprFunction func TestExprFunctionCreation(t *testing.T) { fn := NewExprFunction() if fn == nil { @@ -109,7 +109,7 @@ func TestExprFunctionCreation(t *testing.T) { } } -// TestExprFunctionWithDifferentExpressions 测试不同类型的表达式 +// TestExprFunctionWithDifferentExpressions tests expressions of different types func TestExprFunctionWithDifferentExpressions(t *testing.T) { fn := NewExprFunction() ctx := &FunctionContext{ @@ -121,7 +121,7 @@ func TestExprFunctionWithDifferentExpressions(t *testing.T) { }, } - // 测试数学表达式 + // Test mathematical expressions result, err := fn.Execute(ctx, []any{"x + y"}) if err != nil { t.Errorf("Math expression failed: %v", err) @@ -130,7 +130,7 @@ func TestExprFunctionWithDifferentExpressions(t *testing.T) { t.Errorf("Expected 15, got %v", result) } - // 测试比较表达式 + // Test the comparison expression result, err = fn.Execute(ctx, []any{"x > y"}) if err != nil { t.Errorf("Comparison expression failed: %v", err) @@ -139,7 +139,7 @@ func TestExprFunctionWithDifferentExpressions(t *testing.T) { t.Errorf("Expected true, got %v", result) } - // 测试字符串表达式 + // Test string expressions result, err = fn.Execute(ctx, []any{"name + ' Doe'"}) if err != nil { t.Errorf("String expression failed: %v", err) @@ -148,7 +148,7 @@ func TestExprFunctionWithDifferentExpressions(t *testing.T) { t.Errorf("Expected 'John Doe', got %v", result) } - // 测试布尔表达式 + // Test the Boolean expression result, err = fn.Execute(ctx, []any{"active && true"}) if err != nil { t.Errorf("Boolean expression failed: %v", err) @@ -157,7 +157,7 @@ func TestExprFunctionWithDifferentExpressions(t *testing.T) { t.Errorf("Expected true, got %v", result) } - // 测试复杂表达式 + // Test complex expressions result, err = fn.Execute(ctx, []any{"(x + y) * 2"}) if err != nil { t.Errorf("Complex expression failed: %v", err) @@ -167,7 +167,7 @@ func TestExprFunctionWithDifferentExpressions(t *testing.T) { } } -// TestExprFunctionWithFunctionCalls 测试函数调用表达式 +// TestExprFunctionWithFunctionCalls is a test function call expression func TestExprFunctionWithFunctionCalls(t *testing.T) { fn := NewExprFunction() ctx := &FunctionContext{ @@ -177,7 +177,7 @@ func TestExprFunctionWithFunctionCalls(t *testing.T) { }, } - // 测试abs函数调用 + // Testing the abs function call result, err := fn.Execute(ctx, []any{"abs(-10)"}) if err != nil { t.Errorf("Function call expression failed: %v", err) @@ -186,7 +186,7 @@ func TestExprFunctionWithFunctionCalls(t *testing.T) { t.Errorf("Expected 10, got %v", result) } - // 测试length函数调用 + // Test the length function call result, err = fn.Execute(ctx, []any{"length(text)"}) if err != nil { t.Errorf("Length function call failed: %v", err) @@ -195,7 +195,7 @@ func TestExprFunctionWithFunctionCalls(t *testing.T) { t.Errorf("Expected 11, got %v", result) } - // 测试组合函数调用 + // Test composer function calls result, err = fn.Execute(ctx, []any{"abs(num) + length(text)"}) if err != nil { t.Errorf("Combined function calls failed: %v", err) diff --git a/functions/functions_hash_test.go b/functions/functions_hash_test.go index 9be0afe..e550d76 100644 --- a/functions/functions_hash_test.go +++ b/functions/functions_hash_test.go @@ -4,7 +4,7 @@ import ( "testing" ) -// TestHashFunctions 测试哈希函数的基本功能 +// TestHashFunctions Tests the basic functions of hash functions func TestHashFunctions(t *testing.T) { tests := []struct { name string @@ -70,7 +70,7 @@ func TestHashFunctions(t *testing.T) { } } -// TestHashFunctionValidation 测试哈希函数的参数验证 +// TestHashFunctionValidation: Validation of parameters for the test hash function func TestHashFunctionValidation(t *testing.T) { tests := []struct { name string @@ -126,7 +126,7 @@ func TestHashFunctionValidation(t *testing.T) { } } -// TestHashFunctionErrors 测试哈希函数的错误处理 +// TestHashFunctionErrors tests the error handling of hash functions func TestHashFunctionErrors(t *testing.T) { tests := []struct { name string diff --git a/functions/functions_json.go b/functions/functions_json.go index 21b45eb..fc8649e 100644 --- a/functions/functions_json.go +++ b/functions/functions_json.go @@ -124,7 +124,7 @@ func (f *JsonExtractFunction) Execute(ctx *FunctionContext, args []any) (any, er return val, nil } -// JsonValidFunction 验证JSON格式是否有效 +// JsonValidFunction verifies whether the JSON format is valid type JsonValidFunction struct { *BaseFunction } @@ -150,7 +150,7 @@ func (f *JsonValidFunction) Execute(ctx *FunctionContext, args []any) (any, erro return err == nil, nil } -// JsonTypeFunction 返回JSON值的类型 +// JsonTypeFunction Returns the type of JSON value type JsonTypeFunction struct { *BaseFunction } @@ -195,7 +195,7 @@ func (f *JsonTypeFunction) Execute(ctx *FunctionContext, args []any) (any, error } } -// JsonLengthFunction 返回JSON数组或对象的长度 +// JsonLengthFunction Returns the length of a JSON array or object type JsonLengthFunction struct { *BaseFunction } diff --git a/functions/functions_json_test.go b/functions/functions_json_test.go index 41ecd85..bdc00bc 100644 --- a/functions/functions_json_test.go +++ b/functions/functions_json_test.go @@ -4,7 +4,7 @@ import ( "testing" ) -// 测试JSON函数 +// Test the JSON function func TestJsonFunctions(t *testing.T) { tests := []struct { name string @@ -263,7 +263,7 @@ func TestJsonFunctions(t *testing.T) { } } -// TestJsonFunctionValidation 测试JSON函数参数验证 +// TestJsonFunctionValidation: Validates the JSON function parameters func TestJsonFunctionValidation(t *testing.T) { tests := []struct { name string @@ -342,7 +342,7 @@ func TestJsonFunctionValidation(t *testing.T) { } } -// TestJsonFunctionCreation 测试JSON函数创建 +// TestJsonFunctionCreation TestJsonFunctionCreation Creates JSON functions func TestJsonFunctionCreation(t *testing.T) { tests := []struct { name string @@ -422,7 +422,7 @@ func TestJsonFunctionCreation(t *testing.T) { } } -// 辅助函数:比较结果 +// Auxiliary function: Compare results func compareResults(a, b any) bool { if a == nil && b == nil { return true @@ -431,7 +431,7 @@ func compareResults(a, b any) bool { return false } - // 对于map类型的特殊处理 + // Special handling for map types if mapA, okA := a.(map[string]any); okA { if mapB, okB := b.(map[string]any); okB { if len(mapA) != len(mapB) { @@ -446,7 +446,7 @@ func compareResults(a, b any) bool { } } - // 对于slice类型的特殊处理 + // Special handling for slice types if sliceA, okA := a.([]any); okA { if sliceB, okB := b.([]any); okB { if len(sliceA) != len(sliceB) { diff --git a/functions/functions_math.go b/functions/functions_math.go index 4207f05..168b7f1 100644 --- a/functions/functions_math.go +++ b/functions/functions_math.go @@ -110,7 +110,7 @@ func (f *AsinFunction) Execute(ctx *FunctionContext, args []any) (any, error) { return math.Asin(val), nil } -// AtanFunction 反正切函数 +// AtanFunction arctangent function type AtanFunction struct { *BaseFunction } @@ -133,7 +133,7 @@ func (f *AtanFunction) Execute(ctx *FunctionContext, args []any) (any, error) { return math.Atan(val), nil } -// Atan2Function 两个参数的反正切函数 +// Atan2Function The arctangent function of two parameters type Atan2Function struct { *BaseFunction } @@ -160,7 +160,7 @@ func (f *Atan2Function) Execute(ctx *FunctionContext, args []any) (any, error) { return math.Atan2(y, x), nil } -// BitAndFunction 按位与函数 +// BitAndFunction is a bit by bit and function type BitAndFunction struct { *BaseFunction } @@ -187,7 +187,7 @@ func (f *BitAndFunction) Execute(ctx *FunctionContext, args []any) (any, error) return a & b, nil } -// BitOrFunction 按位或函数 +// BitOrFunction by bit or function type BitOrFunction struct { *BaseFunction } @@ -214,7 +214,7 @@ func (f *BitOrFunction) Execute(ctx *FunctionContext, args []any) (any, error) { return a | b, nil } -// BitXorFunction 按位异或函数 +// BitXorFunction XOR function type BitXorFunction struct { *BaseFunction } @@ -241,7 +241,7 @@ func (f *BitXorFunction) Execute(ctx *FunctionContext, args []any) (any, error) return a ^ b, nil } -// BitNotFunction 按位非函数 +// BitNotFunction is not a bit function type BitNotFunction struct { *BaseFunction } @@ -264,7 +264,7 @@ func (f *BitNotFunction) Execute(ctx *FunctionContext, args []any) (any, error) return ^a, nil } -// CeilingFunction 向上取整函数 +// CeilingFunction is an upward rounding function type CeilingFunction struct { *BaseFunction } @@ -287,7 +287,7 @@ func (f *CeilingFunction) Execute(ctx *FunctionContext, args []any) (any, error) return math.Ceil(val), nil } -// CosFunction 余弦函数 +// CosFunction Cosine function type CosFunction struct { *BaseFunction } @@ -310,7 +310,7 @@ func (f *CosFunction) Execute(ctx *FunctionContext, args []any) (any, error) { return math.Cos(val), nil } -// CoshFunction 双曲余弦函数 +// CoshFunction Hyperbolic Cosine Function type CoshFunction struct { *BaseFunction } @@ -333,7 +333,7 @@ func (f *CoshFunction) Execute(ctx *FunctionContext, args []any) (any, error) { return math.Cosh(val), nil } -// ExpFunction 指数函数 +// ExpFunction Exponential Function type ExpFunction struct { *BaseFunction } @@ -360,7 +360,7 @@ func (f *ExpFunction) Execute(ctx *FunctionContext, args []any) (any, error) { return result, nil } -// FloorFunction 向下取整函数 +// FloorFunction takes the whole function downward type FloorFunction struct { *BaseFunction } @@ -383,7 +383,7 @@ func (f *FloorFunction) Execute(ctx *FunctionContext, args []any) (any, error) { return math.Floor(val), nil } -// LnFunction 自然对数函数 +// LnFunction is a natural logarithmic function type LnFunction struct { *BaseFunction } @@ -409,7 +409,7 @@ func (f *LnFunction) Execute(ctx *FunctionContext, args []any) (any, error) { return math.Log(val), nil } -// LogFunction 以10为底的对数函数 (log的别名) +// LogFunction LogFunction with base 10 (another name for log) type LogFunction struct { *BaseFunction } @@ -435,7 +435,7 @@ func (f *LogFunction) Execute(ctx *FunctionContext, args []any) (any, error) { return math.Log10(val), nil } -// Log10Function 以10为底的对数函数 +// Log10Function is a logarithmic function with base 10 type Log10Function struct { *BaseFunction } @@ -461,7 +461,7 @@ func (f *Log10Function) Execute(ctx *FunctionContext, args []any) (any, error) { return math.Log10(val), nil } -// Log2Function 以2为底的对数函数 +// Log2Function is a logarithmic function with base 2 type Log2Function struct { *BaseFunction } @@ -487,7 +487,7 @@ func (f *Log2Function) Execute(ctx *FunctionContext, args []any) (any, error) { return math.Log2(val), nil } -// ModFunction 取模函数 +// ModFunction type ModFunction struct { *BaseFunction } @@ -517,7 +517,7 @@ func (f *ModFunction) Execute(ctx *FunctionContext, args []any) (any, error) { return math.Mod(x, y), nil } -// RandFunction 随机数函数 +// RandFunction Random number function type RandFunction struct { *BaseFunction } @@ -533,12 +533,12 @@ func (f *RandFunction) Validate(args []any) error { } func (f *RandFunction) Execute(ctx *FunctionContext, args []any) (any, error) { - // 使用当前时间作为种子 + // Use the current time as the seed rand.Seed(time.Now().UnixNano()) return rand.Float64(), nil } -// RoundFunction 四舍五入函数 +// RoundFunction Rounding function type RoundFunction struct { *BaseFunction } @@ -554,7 +554,7 @@ func (f *RoundFunction) Validate(args []any) error { } func (f *RoundFunction) Execute(ctx *FunctionContext, args []any) (any, error) { - // 检查第一个参数是否为nil + // Check if the first parameter is nil if args[0] == nil { return nil, nil } @@ -568,7 +568,7 @@ func (f *RoundFunction) Execute(ctx *FunctionContext, args []any) (any, error) { return math.Round(val), nil } - // 检查第二个参数是否为nil(如果存在) + // Check if the second parameter is nil (if present) if args[1] == nil { return nil, nil } @@ -582,7 +582,7 @@ func (f *RoundFunction) Execute(ctx *FunctionContext, args []any) (any, error) { return math.Round(val*shift) / shift, nil } -// SignFunction 符号函数 +// SignFunction symbolic function type SignFunction struct { *BaseFunction } @@ -611,7 +611,7 @@ func (f *SignFunction) Execute(ctx *FunctionContext, args []any) (any, error) { return 0, nil } -// SinFunction 正弦函数 +// SinFunction type SinFunction struct { *BaseFunction } @@ -634,7 +634,7 @@ func (f *SinFunction) Execute(ctx *FunctionContext, args []any) (any, error) { return math.Sin(val), nil } -// SinhFunction 双曲正弦函数 +// SinhFunction Hyperbolic Sine Function type SinhFunction struct { *BaseFunction } @@ -657,7 +657,7 @@ func (f *SinhFunction) Execute(ctx *FunctionContext, args []any) (any, error) { return math.Sinh(val), nil } -// TanFunction 正切函数 +// TanFunction tangent function type TanFunction struct { *BaseFunction } @@ -680,7 +680,7 @@ func (f *TanFunction) Execute(ctx *FunctionContext, args []any) (any, error) { return math.Tan(val), nil } -// TanhFunction 双曲正切函数 +// TanhFunction hyperbolic tangent function type TanhFunction struct { *BaseFunction } @@ -703,7 +703,7 @@ func (f *TanhFunction) Execute(ctx *FunctionContext, args []any) (any, error) { return math.Tanh(val), nil } -// PowerFunction 幂函数 +// PowerFunction power function type PowerFunction struct { *BaseFunction } diff --git a/functions/functions_math_test.go b/functions/functions_math_test.go index 664a753..b04012a 100644 --- a/functions/functions_math_test.go +++ b/functions/functions_math_test.go @@ -5,7 +5,7 @@ import ( "testing" ) -// TestMathFunctions 测试数学函数的基本功能 +// TestMathFunctions Tests the basic functions of mathematical functions func TestMathFunctions(t *testing.T) { tests := []struct { name string @@ -448,7 +448,7 @@ func TestMathFunctions(t *testing.T) { expected: float64(1), wantErr: false, }, - // 错误处理测试用例 + // Error handling test cases { name: "abs invalid type", funcName: "abs", @@ -485,7 +485,7 @@ func TestMathFunctions(t *testing.T) { if !exists { t.Fatalf("Function %s not found", tt.funcName) } - // 验证参数 + // Validate parameters if err := fn.Validate(tt.args); err != nil { if !tt.wantErr { t.Errorf("Validate() error = %v, wantErr %v", err, tt.wantErr) @@ -509,7 +509,7 @@ func TestMathFunctions(t *testing.T) { }) } - // 特殊测试:rand函数(因为结果是随机的) + // Special test: rand function (since the result is random) t.Run("rand function", func(t *testing.T) { fn, exists := Get("rand") if !exists { @@ -533,7 +533,7 @@ func TestMathFunctions(t *testing.T) { }) } -// TestMathFunctionValidation 测试数学函数的参数验证 +// TestMathFunctionValidation: Tests the parameter validation of mathematical functions func TestMathFunctionValidation(t *testing.T) { tests := []struct { name string @@ -571,7 +571,7 @@ func TestMathFunctionValidation(t *testing.T) { } } -// TestMathFunctionErrors 测试数学函数的错误处理 +// TestMathFunctionErrors tests error handling in mathematical functions func TestMathFunctionErrors(t *testing.T) { tests := []struct { name string @@ -657,7 +657,7 @@ func TestMathFunctionErrors(t *testing.T) { } } -// TestMathFunctionEdgeCases 测试数学函数的边界情况 +// TestMathFunctionEdgeCases tests the boundary state of the mathematical function func TestMathFunctionEdgeCases(t *testing.T) { tests := []struct { name string @@ -740,7 +740,7 @@ func TestMathFunctionEdgeCases(t *testing.T) { } if !tt.wantErr { - // 对于浮点数比较,使用近似相等 + // For floating-point comparisons, approximate equality is used if expected, ok := tt.expected.(float64); ok { if actual, ok := result.(float64); ok { if math.Abs(actual-expected) > 1e-9 { diff --git a/functions/functions_multirow.go b/functions/functions_multirow.go index 0b3187c..3aa9196 100644 --- a/functions/functions_multirow.go +++ b/functions/functions_multirow.go @@ -33,46 +33,46 @@ func (f *UnnestFunction) Execute(ctx *FunctionContext, args []any) (any, error) array := args[0] if array == nil { - // 返回带有unnest标记的空结果 + // Returns an empty result marked with unnest return []any{ map[string]any{ UnnestObjectMarker: true, - UnnestEmptyMarker: true, // 标记这是空unnest结果 + UnnestEmptyMarker: true, // Mark this as an empty unnest result }, }, nil } - // 使用反射检查是否为数组或切片 + // Use reflection to check whether the array is a slice v := reflect.ValueOf(array) if v.Kind() != reflect.Slice && v.Kind() != reflect.Array { return nil, fmt.Errorf("unnest requires an array or slice, got %T", array) } - // 如果数组为空,返回带标记的空数组 + // If the array is empty, it returns an empty array with a mark if v.Len() == 0 { - // 返回带有unnest标记的空结果 + // Returns an empty result marked with unnest return []any{ map[string]any{ UnnestObjectMarker: true, - UnnestEmptyMarker: true, // 标记这是空unnest结果 + UnnestEmptyMarker: true, // Mark this as an empty unnest result }, }, nil } - // 转换为 []any,所有元素都标记为unnest结果 + // Convert to []any, and all elements are marked as unnest results result := make([]any, v.Len()) for i := 0; i < v.Len(); i++ { elem := v.Index(i).Interface() - // 如果数组元素是对象(map),则展开为列 + // If the array element is an object (map), expand it into a column if elemMap, ok := elem.(map[string]any); ok { - // 对于对象,我们返回一个特殊的结构来表示需要展开为列 + // For objects, we return a special structure to indicate that the columns need to be expanded result[i] = map[string]any{ UnnestObjectMarker: true, UnnestDataKey: elemMap, } } else { - // 对于普通元素,也需要标记为unnest结果 + // For ordinary elements, you also need to mark them as unnest results result[i] = map[string]any{ UnnestObjectMarker: true, UnnestDataKey: elem, @@ -93,7 +93,7 @@ func IsUnnestResult(value any) bool { return false } - // 检查数组中是否有任何unnest标记的元素 + // Check if there are any unnest tagged elements in the array for _, item := range slice { if itemMap, ok := item.(map[string]any); ok { if unnest, exists := itemMap[UnnestObjectMarker]; exists { @@ -104,7 +104,7 @@ func IsUnnestResult(value any) bool { } } - // 如果没有找到unnest标记,则不是unnest结果 + // If the unnest tag is not found, it is not the unnest result return false } @@ -120,12 +120,12 @@ func ProcessUnnestResult(value any) []map[string]any { if unnest, exists := itemMap[UnnestObjectMarker]; exists { if unnestBool, ok := unnest.(bool); ok && unnestBool { if data, exists := itemMap[UnnestDataKey]; exists { - // 检查数据是否为对象(map) + // Check if the data is an object (map) if dataMap, ok := data.(map[string]any); ok { - // 对象数据直接展开为列 + // Expand object data directly into columns rows = append(rows, dataMap) } else { - // 普通数据使用默认字段名 + // Ordinary data uses the default field name row := map[string]any{ DefaultValueKey: data, } @@ -136,7 +136,7 @@ func ProcessUnnestResult(value any) []map[string]any { } } } - // 对于非标记元素,创建一个包含单个值的行(向后兼容) + // For non-marked elements, create a row containing a single value (backward compatible) row := map[string]any{ DefaultValueKey: item, } @@ -157,19 +157,19 @@ func ProcessUnnestResultWithFieldName(value any, fieldName string) []map[string] if itemMap, ok := item.(map[string]any); ok { if unnest, exists := itemMap[UnnestObjectMarker]; exists { if unnestBool, ok := unnest.(bool); ok && unnestBool { - // 检查是否为空unnest结果 + // Check if the result is empty if itemMap[UnnestEmptyMarker] == true { - // 空unnest结果,返回空数组 + // Empty unnest result returns an empty array return []map[string]any{} } if data, exists := itemMap[UnnestDataKey]; exists { - // 检查数据是否为对象(map) + // Check if the data is an object (map) if dataMap, ok := data.(map[string]any); ok { - // 对象数据直接展开为列 + // Expand object data directly into columns rows = append(rows, dataMap) } else { - // 普通数据使用指定字段名 + // Ordinary data uses specified field names row := map[string]any{ fieldName: data, } @@ -180,7 +180,7 @@ func ProcessUnnestResultWithFieldName(value any, fieldName string) []map[string] } } } - // 对于非标记元素,使用指定的字段名创建行(向后兼容) + // For non-marked elements, create rows using specified field names (backward compatible) row := map[string]any{ fieldName: item, } diff --git a/functions/functions_multirow_test.go b/functions/functions_multirow_test.go index b1d2aa7..9f1ee9f 100644 --- a/functions/functions_multirow_test.go +++ b/functions/functions_multirow_test.go @@ -9,7 +9,7 @@ func TestUnnestFunction(t *testing.T) { fn := NewUnnestFunction() ctx := &FunctionContext{} - // 测试基本unnest功能 + // Test basic unnest functionality args := []any{[]any{"a", "b", "c"}} result, err := fn.Execute(ctx, args) if err != nil { @@ -33,7 +33,7 @@ func TestUnnestFunction(t *testing.T) { t.Errorf("UnnestFunction = %v, want %v", result, expected) } - // 测试对象数组unnest + // The array of test objects is unnest args = []any{ []any{ map[string]any{"name": "Alice", "age": 25}, @@ -58,13 +58,13 @@ func TestUnnestFunction(t *testing.T) { t.Errorf("UnnestFunction = %v, want %v", result, expected) } - // 测试空数组 + // Test the empty array args = []any{[]any{}} result, err = fn.Execute(ctx, args) if err != nil { t.Errorf("UnnestFunction should not return error for empty array: %v", err) } - // 空数组应该返回带有空标记的结果 + // An empty array should return results with an empty flag expectedEmpty := []any{ map[string]any{ "__unnest_object__": true, @@ -75,13 +75,13 @@ func TestUnnestFunction(t *testing.T) { t.Errorf("UnnestFunction empty array = %v, want %v", result, expectedEmpty) } - // 测试nil参数 + // Test the nil parameters args = []any{nil} result, err = fn.Execute(ctx, args) if err != nil { t.Errorf("UnnestFunction should not return error for nil: %v", err) } - // nil应该返回带有空标记的结果 + // nil should return results with an empty marker expectedNil := []any{ map[string]any{ "__unnest_object__": true, @@ -92,21 +92,21 @@ func TestUnnestFunction(t *testing.T) { t.Errorf("UnnestFunction nil = %v, want %v", result, expectedNil) } - // 测试错误参数数量 + // Test the number of error parameters args = []any{} err = fn.Validate(args) if err == nil { t.Errorf("UnnestFunction should return error for no arguments") } - // 测试非数组参数 + // Test non-array parameters args = []any{"not an array"} _, err = fn.Execute(ctx, args) if err == nil { t.Errorf("UnnestFunction should return error for non-array argument") } - // 测试数组类型 + // Test array types args = []any{[3]string{"x", "y", "z"}} result, err = fn.Execute(ctx, args) if err != nil { @@ -131,7 +131,7 @@ func TestUnnestFunction(t *testing.T) { } } -// TestUnnestFunctionCreation 测试UnnestFunction创建 +// TestUnnestFunctionCreation Tests UnnestFunction creation func TestUnnestFunctionCreation(t *testing.T) { fn := NewUnnestFunction() if fn == nil { @@ -172,13 +172,13 @@ func TestUnnestFunctionCreation(t *testing.T) { } func TestIsUnnestResult(t *testing.T) { - // 测试非unnest结果 + // Test for non-unnest results normalSlice := []any{"a", "b", "c"} if IsUnnestResult(normalSlice) { t.Errorf("IsUnnestResult should return false for normal slice") } - // 测试unnest结果 + // Test the unnest results unnestSlice := []any{ map[string]any{ "__unnest_object__": true, @@ -192,7 +192,7 @@ func TestIsUnnestResult(t *testing.T) { t.Errorf("IsUnnestResult should return true for unnest slice") } - // 测试混合结果 + // Test mixed results mixedSlice := []any{ "normal", map[string]any{ @@ -207,14 +207,14 @@ func TestIsUnnestResult(t *testing.T) { t.Errorf("IsUnnestResult should return true for mixed slice") } - // 测试非切片类型 + // Test non-slice types if IsUnnestResult("not a slice") { t.Errorf("IsUnnestResult should return false for non-slice") } } func TestProcessUnnestResult(t *testing.T) { - // 测试处理普通数组 + // Test processing of ordinary arrays normalSlice := []any{"a", "b", "c"} result := ProcessUnnestResult(normalSlice) expected := []map[string]any{ @@ -226,7 +226,7 @@ func TestProcessUnnestResult(t *testing.T) { t.Errorf("ProcessUnnestResult normal slice = %v, want %v", result, expected) } - // 测试处理对象数组 + // Test the array of processed objects objectSlice := []any{ map[string]any{ "__unnest_object__": true, @@ -252,7 +252,7 @@ func TestProcessUnnestResult(t *testing.T) { t.Errorf("ProcessUnnestResult object slice = %v, want %v", result, expected) } - // 测试混合数组 + // Test mixed arrays mixedSlice := []any{ "normal", map[string]any{ @@ -274,7 +274,7 @@ func TestProcessUnnestResult(t *testing.T) { t.Errorf("ProcessUnnestResult mixed slice = %v, want %v", result, expected) } - // 测试非切片类型 + // Test non-slice types result = ProcessUnnestResult("not a slice") if result != nil { t.Errorf("ProcessUnnestResult should return nil for non-slice") diff --git a/functions/functions_string.go b/functions/functions_string.go index 94396c4..6cbf2a6 100644 --- a/functions/functions_string.go +++ b/functions/functions_string.go @@ -123,7 +123,7 @@ func (f *LowerFunction) Execute(ctx *FunctionContext, args []any) (any, error) { return strings.ToLower(str), nil } -// TrimFunction 去除首尾空格函数 +// TrimFunction removes spaces at the beginning and end type TrimFunction struct { *BaseFunction } @@ -146,7 +146,7 @@ func (f *TrimFunction) Execute(ctx *FunctionContext, args []any) (any, error) { return strings.TrimSpace(str), nil } -// FormatFunction 格式化函数 +// FormatFunction formatting function type FormatFunction struct { *BaseFunction } @@ -164,20 +164,20 @@ func (f *FormatFunction) Validate(args []any) error { func (f *FormatFunction) Execute(ctx *FunctionContext, args []any) (any, error) { value := args[0] - // 如果只有一个参数,转换为字符串 + // If there is only one parameter, convert it to a string if len(args) == 1 { return cast.ToStringE(value) } - // 如果有格式参数 + // If there are format parameters, pattern, err := cast.ToStringE(args[1]) if err != nil { return nil, err } - // 处理数值格式化 + // Handle numerical formatting if val, err := cast.ToFloat64E(value); err == nil { - // 简单的数值格式化支持 + // Simple numerical formatting support switch pattern { case "0": return fmt.Sprintf("%.0f", val), nil @@ -188,7 +188,7 @@ func (f *FormatFunction) Execute(ctx *FunctionContext, args []any) (any, error) case "0.000": return fmt.Sprintf("%.3f", val), nil default: - // 尝试解析精度参数 + // Try to analyze the accuracy parameters if strings.Contains(pattern, ".") { precision := len(strings.Split(pattern, ".")[1]) return fmt.Sprintf("%."+fmt.Sprintf("%d", precision)+"f", val), nil @@ -197,17 +197,17 @@ func (f *FormatFunction) Execute(ctx *FunctionContext, args []any) (any, error) } } - // 字符串格式化 + // String formatting str, err := cast.ToStringE(value) if err != nil { return nil, err } - // 如果有第三个参数(locale),这里简化处理 + // If there is a third parameter (locale), this simplifies the handling here return str, nil } -// EndswithFunction 检查字符串是否以指定后缀结尾 +// EndswithFunction checks whether the string ends with a specified suffix type EndswithFunction struct { *BaseFunction } @@ -234,7 +234,7 @@ func (f *EndswithFunction) Execute(ctx *FunctionContext, args []any) (any, error return strings.HasSuffix(str, suffix), nil } -// StartswithFunction 检查字符串是否以指定前缀开始 +// StartswithFunction checks whether the string starts with a specified prefix type StartswithFunction struct { *BaseFunction } @@ -261,7 +261,7 @@ func (f *StartswithFunction) Execute(ctx *FunctionContext, args []any) (any, err return strings.HasPrefix(str, prefix), nil } -// IndexofFunction 返回子字符串在字符串中的位置 +// IndexofFunction returns the position of the substring within the string type IndexofFunction struct { *BaseFunction } @@ -288,7 +288,7 @@ func (f *IndexofFunction) Execute(ctx *FunctionContext, args []any) (any, error) return int64(strings.Index(str, substr)), nil } -// SubstringFunction 提取子字符串。 +// SubstringFunction extracts substrings. // // Dialect note: positions are 0-based and a negative start counts from the end // (Go style), which DEVIATES from ANSI SQL / MySQL / PostgreSQL where @@ -359,7 +359,7 @@ func substringByRune(str string, start, length int64, hasLength bool) (string, e return string(runes[start:end]), nil } -// ReplaceFunction 替换字符串中的内容 +// ReplaceFunction replaces the contents of the string type ReplaceFunction struct { *BaseFunction } @@ -390,7 +390,7 @@ func (f *ReplaceFunction) Execute(ctx *FunctionContext, args []any) (any, error) return strings.ReplaceAll(str, old, new), nil } -// SplitFunction 按分隔符分割字符串 +// SplitFunction Splits strings by separator type SplitFunction struct { *BaseFunction } @@ -417,7 +417,7 @@ func (f *SplitFunction) Execute(ctx *FunctionContext, args []any) (any, error) { return strings.Split(str, delimiter), nil } -// LpadFunction 左填充字符串 +// LpadFunction left fills the string type LpadFunction struct { *BaseFunction } @@ -463,7 +463,7 @@ func (f *LpadFunction) Execute(ctx *FunctionContext, args []any) (any, error) { return padStr[:padLen] + str, nil } -// RpadFunction 右填充字符串 +// RpadFunction to fill the right string type RpadFunction struct { *BaseFunction } @@ -509,7 +509,7 @@ func (f *RpadFunction) Execute(ctx *FunctionContext, args []any) (any, error) { return str + padStr[:padLen], nil } -// LtrimFunction 去除左侧空白字符 +// LtrimFunction removes the whitespace characters on the left type LtrimFunction struct { *BaseFunction } @@ -534,7 +534,7 @@ func (f *LtrimFunction) Execute(ctx *FunctionContext, args []any) (any, error) { }), nil } -// RtrimFunction 去除右侧空白字符 +// RtrimFunction removes the whitespace character on the right type RtrimFunction struct { *BaseFunction } @@ -559,7 +559,7 @@ func (f *RtrimFunction) Execute(ctx *FunctionContext, args []any) (any, error) { }), nil } -// RegexpMatchesFunction 正则表达式匹配 +// RegexpMatchesFunction Regular expression matching type RegexpMatchesFunction struct { *BaseFunction } @@ -591,7 +591,7 @@ func (f *RegexpMatchesFunction) Execute(ctx *FunctionContext, args []any) (any, return matched, nil } -// RegexpReplaceFunction 正则表达式替换 +// RegexpReplaceFunction replaces regular expressions type RegexpReplaceFunction struct { *BaseFunction } @@ -627,7 +627,7 @@ func (f *RegexpReplaceFunction) Execute(ctx *FunctionContext, args []any) (any, return re.ReplaceAllString(str, replacement), nil } -// RegexpSubstringFunction 正则表达式提取子字符串 +// RegexpSubstringFunction Extracts substrings from regular expressions type RegexpSubstringFunction struct { *BaseFunction } diff --git a/functions/functions_string_test.go b/functions/functions_string_test.go index 26da313..a980af3 100644 --- a/functions/functions_string_test.go +++ b/functions/functions_string_test.go @@ -82,7 +82,7 @@ func TestNewStringFunctions(t *testing.T) { if !exists { t.Fatalf("Function %s not found", tt.funcName) } - // 验证参数 + // Validate parameters if err := fn.Validate(tt.args); err != nil { t.Errorf("Validate() error = %v", err) return @@ -102,7 +102,7 @@ func TestNewStringFunctions(t *testing.T) { return } - // 特殊处理 split 函数的结果比较 + // Special handling of split function results comparison if tt.funcName == "split" { expectedSlice, ok := tt.expected.([]string) if !ok { @@ -133,7 +133,7 @@ func TestNewStringFunctions(t *testing.T) { } } -// TestStringFunctionValidation 测试字符串函数的参数验证 +// TestStringFunctionValidation: Parameter validation of the test string function func TestStringFunctionValidation(t *testing.T) { tests := []struct { name string @@ -183,7 +183,7 @@ func TestStringFunctionValidation(t *testing.T) { } } -// TestStringFunctionErrors 测试字符串函数的错误处理 +// TestStringFunctionErrors: Handles errors in string functions func TestStringFunctionErrors(t *testing.T) { tests := []struct { name string @@ -257,7 +257,7 @@ func TestStringFunctionErrors(t *testing.T) { } } -// TestStringFunctionEdgeCases 测试字符串函数的边界情况 +// TestStringFunctionEdgeCases tests the boundary state of the string function func TestStringFunctionEdgeCases(t *testing.T) { tests := []struct { name string @@ -322,7 +322,7 @@ func TestStringFunctionEdgeCases(t *testing.T) { expected: []string{"h", "e", "l", "l", "o"}, wantErr: false, }, - // 新增测试用例 + // Add test cases { name: "length array", function: NewLengthFunction(), @@ -384,14 +384,14 @@ func TestStringFunctionEdgeCases(t *testing.T) { } if !tt.wantErr { - // 特殊处理 split 函数的结果比较 + // Special handling of split function results comparison if tt.name == "split empty delimiter" { expectedSlice, ok := tt.expected.([]string) if !ok { t.Errorf("Expected result is not []string") return } - // split函数返回的是[]string类型 + // The split function returns the []string type actualSlice, ok := result.([]string) if !ok { t.Errorf("Actual result is not []string") diff --git a/functions/functions_test.go b/functions/functions_test.go index fe2885e..2bd6a89 100644 --- a/functions/functions_test.go +++ b/functions/functions_test.go @@ -11,7 +11,7 @@ import ( ) func TestBasicFunctionRegistry(t *testing.T) { - // 测试基本函数注册 + // Test basic function registration tests := []struct { name string functionName string @@ -35,7 +35,7 @@ func TestBasicFunctionRegistry(t *testing.T) { }) } - // 测试不存在的函数 + // Test functions that don't exist _, exists := Get("nonexistent") assert.False(t, exists, "nonexistent function should not be found") } @@ -45,7 +45,7 @@ func TestNewMathFunctions(t *testing.T) { Data: map[string]any{}, } - // 表驱动测试用例 + // Table-driven test cases tests := []struct { name string functionName string @@ -53,7 +53,7 @@ func TestNewMathFunctions(t *testing.T) { expected any expectError bool errorMsg string - delta float64 // 用于浮点数比较的精度 + delta float64 // Accuracy for floating-point comparison }{ // Log function tests {"log valid", "log", []any{10.0}, 1.0, false, "", 1e-10}, @@ -114,7 +114,7 @@ func TestNewMathFunctions(t *testing.T) { }) } - // 特殊测试:rand函数(因为结果是随机的) + // Special test: rand function (since the result is random) t.Run("rand function", func(t *testing.T) { fn, exists := Get("rand") assert.True(t, exists) @@ -134,7 +134,7 @@ func TestFunctionExecution(t *testing.T) { Data: map[string]any{}, } - // 函数执行测试用例 + // The function executes test cases tests := []struct { name string functionName string @@ -142,7 +142,7 @@ func TestFunctionExecution(t *testing.T) { expected any expectError bool }{ - // 数学函数 + // Mathematical functions {"abs with positive", "abs", []any{5.5}, 5.5, false}, {"abs with negative", "abs", []any{-5.5}, 5.5, false}, {"abs with zero", "abs", []any{0}, 0.0, false}, @@ -151,12 +151,12 @@ func TestFunctionExecution(t *testing.T) { {"sqrt with zero", "sqrt", []any{0}, 0.0, false}, {"sqrt with negative", "sqrt", []any{-1}, nil, true}, - // 时间日期函数 + // Time-date function {"now basic", "now", []any{}, time.Now().Unix(), false}, {"current_time basic", "current_time", []any{}, time.Now().Format("15:04:05"), false}, {"current_date basic", "current_date", []any{}, time.Now().Format("2006-01-02"), false}, - // 新增数学函数测试 + // Added mathematical function tests {"acos valid", "acos", []any{0.5}, math.Acos(0.5), false}, {"acos invalid", "acos", []any{2.0}, nil, true}, {"asin valid", "asin", []any{0.5}, math.Asin(0.5), false}, @@ -178,7 +178,7 @@ func TestFunctionExecution(t *testing.T) { {"ln invalid", "ln", []any{-1.0}, nil, true}, {"power valid", "power", []any{2.0, 3.0}, 8.0, false}, - // 字符串函数 + // String function {"concat basic", "concat", []any{"hello", " ", "world"}, "hello world", false}, {"concat single", "concat", []any{"hello"}, "hello", false}, {"concat numbers", "concat", []any{1, 2, 3}, "123", false}, @@ -189,7 +189,7 @@ func TestFunctionExecution(t *testing.T) { {"lower basic", "lower", []any{"HELLO"}, "hello", false}, {"lower mixed", "lower", []any{"Hello World"}, "hello world", false}, - // 转换函数 + // Conversion function {"cast to int64", "cast", []any{"123", "int64"}, int64(123), false}, {"cast to float64", "cast", []any{"123.45", "float64"}, 123.45, false}, {"cast to string", "cast", []any{123, "string"}, "123", false}, @@ -212,7 +212,7 @@ func TestFunctionExecution(t *testing.T) { {"decode invalid base64", "decode", []any{"invalid!", "base64"}, nil, true}, {"decode invalid hex", "decode", []any{"invalid!", "hex"}, nil, true}, - // 聚合函数 + // Aggregate function {"sum basic", "sum", []any{1, 2, 3}, 6.0, false}, {"sum float", "sum", []any{1.5, 2.5}, 4.0, false}, {"avg basic", "avg", []any{1, 2, 3}, 2.0, false}, @@ -220,17 +220,17 @@ func TestFunctionExecution(t *testing.T) { {"max basic", "max", []any{3, 1, 2}, 3.0, false}, {"count basic", "count", []any{1, 2, 3, 4, 5}, int64(5), false}, - // 错误情况 + // Error case {"hex2dec invalid", "hex2dec", []any{"xyz"}, nil, true}, - // 字符串函数 + // String function {"trim basic", "trim", []any{" hello world "}, "hello world", false}, {"trim empty", "trim", []any{""}, "", false}, {"format number 2 decimals", "format", []any{123.456, "0.00"}, "123.46", false}, {"format number 0 decimals", "format", []any{123.456, "0"}, "123", false}, {"format string only", "format", []any{"hello"}, "hello", false}, - // 新增的聚合函数 + // A new aggregation function {"collect basic", "collect", []any{1, 2, 3}, []any{1, 2, 3}, false}, {"last_value basic", "last_value", []any{1, 2, 3, 4}, 4, false}, {"merge_agg basic", "merge_agg", []any{"a", "b", "c"}, "a,b,c", false}, @@ -238,8 +238,6 @@ func TestFunctionExecution(t *testing.T) { {"deduplicate basic", "deduplicate", []any{1, 2, 2, 3, 3, 3}, []any{1, 2, 3}, false}, {"var basic", "var", []any{1.0, 2.0, 3.0, 4.0, 5.0}, 2.0, false}, {"vars basic", "vars", []any{1.0, 2.0, 3.0, 4.0, 5.0}, 2.5, false}, - - } for _, tt := range tests { @@ -267,7 +265,7 @@ func TestFunctionExecution(t *testing.T) { } case int64: if tt.functionName == "now" { - // now 返回 time.Time,只校验类型 + // now returns time.Time, only type check _, ok := result.(time.Time) assert.True(t, ok, "now function should return time.Time") } else { @@ -275,7 +273,7 @@ func TestFunctionExecution(t *testing.T) { } case string: if tt.functionName == "current_time" || tt.functionName == "current_date" { - // 对于时间日期函数,我们只检查格式是否正确 + // For the time-date function, we only check whether the format is correct resultStr, ok := result.(string) assert.True(t, ok, "%s function should return string", tt.functionName) if tt.functionName == "current_time" { @@ -298,7 +296,7 @@ func TestFunctionExecution(t *testing.T) { } func TestFunctionValidation(t *testing.T) { - // 参数验证测试用例 + // Parameter validation test cases tests := []struct { name string functionName string @@ -306,31 +304,31 @@ func TestFunctionValidation(t *testing.T) { expectError bool description string }{ - // abs 函数 - 需要1个参数 + // abs function - requires 1 parameter {"abs no args", "abs", []any{}, true, "abs requires 1 argument"}, {"abs too many args", "abs", []any{1.0, 2.0}, true, "abs accepts only 1 argument"}, {"abs correct args", "abs", []any{1.0}, false, "abs should accept 1 argument"}, - // 时间日期函数参数验证 + // Time and date function parameter verification {"current_time with args", "current_time", []any{1}, true, "current_time should not accept arguments"}, {"current_date with args", "current_date", []any{1}, true, "current_date should not accept arguments"}, - // concat 函数 - 需要至少1个参数 + // concat function - requires at least 1 parameter {"concat no args", "concat", []any{}, true, "concat requires at least 1 argument"}, {"concat one arg", "concat", []any{"hello"}, false, "concat should accept 1 argument"}, {"concat multiple args", "concat", []any{"a", "b", "c"}, false, "concat should accept multiple arguments"}, - // cast 函数 - 需要恰好2个参数 + // cast function - requires exactly 2 parameters {"cast no args", "cast", []any{}, true, "cast requires 2 arguments"}, {"cast one arg", "cast", []any{"123"}, true, "cast requires 2 arguments"}, {"cast correct args", "cast", []any{"123", "int64"}, false, "cast should accept 2 arguments"}, {"cast too many args", "cast", []any{"123", "int64", "extra"}, true, "cast accepts only 2 arguments"}, - // now 函数 - 不需要参数 + // now function - no parameters required {"now no args", "now", []any{}, false, "now should accept no arguments"}, {"now with args", "now", []any{1}, true, "now should not accept arguments"}, - // 新增数学函数参数验证 + // Added mathematical function parameter validation {"acos no args", "acos", []any{}, true, "acos requires 1 argument"}, {"acos too many args", "acos", []any{1.0, 2.0}, true, "acos accepts only 1 argument"}, {"atan2 no args", "atan2", []any{}, true, "atan2 requires 2 arguments"}, @@ -345,7 +343,7 @@ func TestFunctionValidation(t *testing.T) { {"power one arg", "power", []any{2.0}, true, "power requires 2 arguments"}, {"power too many args", "power", []any{2.0, 3.0, 4.0}, true, "power accepts only 2 arguments"}, - // 转换函数参数验证 + // Transform function parameters for verification {"encode no args", "encode", []any{}, true, "encode requires 2 arguments"}, {"encode one arg", "encode", []any{"hello"}, true, "encode requires 2 arguments"}, {"encode three args", "encode", []any{"hello", "base64", "extra"}, true, "encode requires exactly 2 arguments"}, @@ -357,7 +355,7 @@ func TestFunctionValidation(t *testing.T) { {"decode invalid input type", "decode", []any{123, "base64"}, true, "decode input must be a string"}, {"decode invalid format type", "decode", []any{"aGVsbG8=", 123}, true, "decode format must be a string"}, - // 新增函数的验证测试 + // Added function validation tests {"trim no args", "trim", []any{}, true, "function trim requires at least 1 arguments"}, {"trim too many args", "trim", []any{"hello", "world"}, true, "function trim accepts at most 1 arguments"}, {"format too many args", "format", []any{"hello", "pattern", "locale", "extra"}, true, "function format accepts at most 3 arguments"}, @@ -383,7 +381,7 @@ func TestFunctionValidation(t *testing.T) { } func TestFunctionTypes(t *testing.T) { - // 函数类型分类测试 + // Function type classification test tests := []struct { functionType FunctionType functions []string @@ -406,7 +404,7 @@ func TestFunctionTypes(t *testing.T) { assert.GreaterOrEqual(t, len(functions), len(tt.functions), "should have at least %d functions of type %s", len(tt.functions), tt.functionType) - // 验证特定函数存在 + // Verify the existence of specific functions functionNames := make(map[string]bool) for _, fn := range functions { functionNames[fn.GetName()] = true @@ -421,7 +419,7 @@ func TestFunctionTypes(t *testing.T) { } func TestCustomFunction(t *testing.T) { - // 注册自定义函数 + // Register custom functions err := RegisterCustomFunction("double2", TypeCustom, "自定义函数", "将数值乘以2", 1, 1, func(ctx *FunctionContext, args []any) (any, error) { val := cast.ToFloat64(args[0]) @@ -429,7 +427,7 @@ func TestCustomFunction(t *testing.T) { }) assert.NoError(t, err) - // 测试自定义函数 + // Test custom functions tests := []struct { name string args []any @@ -456,7 +454,7 @@ func TestCustomFunction(t *testing.T) { }) } - // 清理 + // Cleanup Unregister("double2") } @@ -465,7 +463,7 @@ func TestComplexFunctionCombinations(t *testing.T) { Data: map[string]any{}, } - // 测试复杂函数组合 + // Test complex function combinations tests := []struct { name string description string @@ -525,7 +523,7 @@ func TestComplexFunctionCombinations(t *testing.T) { } } -// TestCaseInsensitiveFunctions 测试函数大小写不敏感 +// TestCaseInsensitiveFunctions: Case-insensitive test functions func TestCaseInsensitiveFunctions(t *testing.T) { tests := []struct { name string @@ -559,7 +557,7 @@ func TestCaseInsensitiveFunctions(t *testing.T) { } } -// TestConcatFunctionCaseInsensitive 测试CONCAT函数的大小写不敏感执行 +// TestConcatFunctionCaseInsensitive Executes the case-insensitive test for the CONCAT function func TestConcatFunctionCaseInsensitive(t *testing.T) { tests := []struct { name string @@ -589,13 +587,13 @@ func TestConcatFunctionCaseInsensitive(t *testing.T) { } } -// TestStringFunctionsCaseInsensitive 测试所有字符串函数的大小写不敏感 +// TestStringFunctionsCaseInsensitive tests all string functions for case insensitivity func TestStringFunctionsCaseInsensitive(t *testing.T) { ctx := &FunctionContext{ Data: make(map[string]any), } - // 测试UPPER函数 + // Test the UPPER function t.Run("UPPER函数大小写不敏感", func(t *testing.T) { functionNames := []string{"upper", "UPPER", "Upper", "uPpEr"} for _, name := range functionNames { @@ -608,7 +606,7 @@ func TestStringFunctionsCaseInsensitive(t *testing.T) { } }) - // 测试LOWER函数 + // Test the LOWER function t.Run("LOWER函数大小写不敏感", func(t *testing.T) { functionNames := []string{"lower", "LOWER", "Lower", "lOwEr"} for _, name := range functionNames { @@ -621,7 +619,7 @@ func TestStringFunctionsCaseInsensitive(t *testing.T) { } }) - // 测试LENGTH函数 + // Test the LENGTH function t.Run("LENGTH函数大小写不敏感", func(t *testing.T) { functionNames := []string{"length", "LENGTH", "Length", "lEnGtH"} for _, name := range functionNames { @@ -634,7 +632,7 @@ func TestStringFunctionsCaseInsensitive(t *testing.T) { } }) - // 测试TRIM函数 + // Test the TRIM function t.Run("TRIM函数大小写不敏感", func(t *testing.T) { functionNames := []string{"trim", "TRIM", "Trim", "tRiM"} for _, name := range functionNames { @@ -648,13 +646,13 @@ func TestStringFunctionsCaseInsensitive(t *testing.T) { }) } -// TestMathFunctionsCaseInsensitive 测试数学函数的大小写不敏感 +// TestMathFunctionsCaseInsensitive tests that math functions are case-insensitive func TestMathFunctionsCaseInsensitive(t *testing.T) { ctx := &FunctionContext{ Data: make(map[string]any), } - // 测试ABS函数 + // Testing the ABS function t.Run("ABS函数大小写不敏感", func(t *testing.T) { functionNames := []string{"abs", "ABS", "Abs", "aBs"} for _, name := range functionNames { @@ -667,7 +665,7 @@ func TestMathFunctionsCaseInsensitive(t *testing.T) { } }) - // 测试SQRT函数 + // Test the SQRT function t.Run("SQRT函数大小写不敏感", func(t *testing.T) { functionNames := []string{"sqrt", "SQRT", "Sqrt", "sQrT"} for _, name := range functionNames { @@ -681,9 +679,9 @@ func TestMathFunctionsCaseInsensitive(t *testing.T) { }) } -// TestAggregationFunctionsCaseInsensitive 测试聚合函数的大小写不敏感 +// TestAggregationFunctionsCaseInsensitive The case insensitive of the test aggregation function func TestAggregationFunctionsCaseInsensitive(t *testing.T) { - // 测试SUM函数 + // Test the SUM function t.Run("SUM函数大小写不敏感", func(t *testing.T) { functionNames := []string{"sum", "SUM", "Sum", "sUm"} for _, name := range functionNames { @@ -693,7 +691,7 @@ func TestAggregationFunctionsCaseInsensitive(t *testing.T) { } }) - // 测试AVG函数 + // Testing the AVG function t.Run("AVG函数大小写不敏感", func(t *testing.T) { functionNames := []string{"avg", "AVG", "Avg", "aVg"} for _, name := range functionNames { @@ -703,7 +701,7 @@ func TestAggregationFunctionsCaseInsensitive(t *testing.T) { } }) - // 测试COUNT函数 + // Test the COUNT function t.Run("COUNT函数大小写不敏感", func(t *testing.T) { functionNames := []string{"count", "COUNT", "Count", "cOuNt"} for _, name := range functionNames { @@ -714,9 +712,9 @@ func TestAggregationFunctionsCaseInsensitive(t *testing.T) { }) } -// TestFunctionAliases 测试函数别名功能 +// TestFunctionAliases TestFunction aliases func TestFunctionAliases(t *testing.T) { - // 测试 power 函数的 pow 别名 + // Test the pow alias of the Power function powerFunc, exists := Get("power") if !exists { t.Fatal("power function not found") @@ -727,12 +725,12 @@ func TestFunctionAliases(t *testing.T) { t.Fatal("pow alias not found") } - // 验证别名指向同一个函数实例 + // Verify that the alias points to the same function instance if powerFunc != powFunc { t.Error("pow alias should point to the same function as power") } - // 测试 length 函数的 len 别名 + // Test the len alias of the length function lengthFunc, exists := Get("length") if !exists { t.Fatal("length function not found") @@ -743,12 +741,12 @@ func TestFunctionAliases(t *testing.T) { t.Fatal("len alias not found") } - // 验证别名指向同一个函数实例 + // Verify that the alias points to the same function instance if lengthFunc != lenFunc { t.Error("len alias should point to the same function as length") } - // 测试 ceiling 函数的 ceil 别名 + // Test the ceil alias for the ceiling function ceilingFunc, exists := Get("ceiling") if !exists { t.Fatal("ceiling function not found") @@ -759,12 +757,12 @@ func TestFunctionAliases(t *testing.T) { t.Fatal("ceil alias not found") } - // 验证别名指向同一个函数实例 + // Verify that the alias points to the same function instance if ceilingFunc != ceilFunc { t.Error("ceil alias should point to the same function as ceiling") } - // 验证别名列表 + // Verify the aliases list aliases := powerFunc.GetAliases() if len(aliases) != 1 || aliases[0] != "pow" { t.Errorf("Expected aliases [pow], got %v", aliases) @@ -781,8 +779,8 @@ func TestFunctionAliases(t *testing.T) { } } -// TestFunctionAliasExecution 测试通过别名执行函数 -// TestExecuteFunction 测试Execute函数的各种场景 +// TestFunctionAliasExecution executes the function by using an alias +// TestExecuteFunction tests various scenarios for execute func TestExecuteFunction(t *testing.T) { ctx := &FunctionContext{ Data: map[string]any{ @@ -791,65 +789,65 @@ func TestExecuteFunction(t *testing.T) { }, } - // 测试正常执行 + // The test was performed normally result, err := Execute("abs", ctx, []any{-5}) assert.NoError(t, err) assert.Equal(t, 5.0, result) - // 测试函数不存在 + // The test function does not exist result, err = Execute("nonexistent_function", ctx, []any{1}) assert.Error(t, err) assert.Nil(t, result) assert.Contains(t, err.Error(), "function nonexistent_function not found") - // 测试参数验证失败 + // Test parameter verification failed result, err = Execute("abs", ctx, []any{}) assert.Error(t, err) assert.Nil(t, result) assert.Contains(t, err.Error(), "validation failed") - // 测试参数过多 + // Too many test parameters result, err = Execute("abs", ctx, []any{1, 2, 3}) assert.Error(t, err) assert.Nil(t, result) assert.Contains(t, err.Error(), "validation failed") } -// TestCustomFunctionValidate 测试CustomFunction的Validate方法 +// TestCustomFunctionValidate tests the Validate method of CustomFunction func TestCustomFunctionValidate(t *testing.T) { - // 注册一个自定义函数 + // Register a custom function err := RegisterCustomFunction("test_custom", TypeMath, "test", "test function", 1, 2, func(ctx *FunctionContext, args []any) (any, error) { return args[0], nil }) assert.NoError(t, err) - // 获取自定义函数 + // Get the custom function fn, exists := Get("test_custom") assert.True(t, exists) - // 测试参数数量正确 + // The number of test parameters is correct err = fn.Validate([]any{1}) assert.NoError(t, err) err = fn.Validate([]any{1, 2}) assert.NoError(t, err) - // 测试参数数量不足 + // Insufficient number of test parameters err = fn.Validate([]any{}) assert.Error(t, err) - // 测试参数数量过多 + // Too many test parameters err = fn.Validate([]any{1, 2, 3}) assert.Error(t, err) - // 清理 + // Cleanup Unregister("test_custom") } -// TestRegisterCustomFunctionErrors 测试RegisterCustomFunction的错误情况 +// TestRegisterCustomFunctionErrors Tests for RegisterCustomFunction errors func TestRegisterCustomFunctionErrors(t *testing.T) { - // 测试空函数名 + // The test empty function name err := RegisterCustomFunction("", TypeMath, "test", "test function", 1, 2, func(ctx *FunctionContext, args []any) (any, error) { return nil, nil @@ -857,21 +855,21 @@ func TestRegisterCustomFunctionErrors(t *testing.T) { assert.Error(t, err) assert.Contains(t, err.Error(), "function name cannot be empty") - // 测试正常注册 + // Test normal registration err = RegisterCustomFunction("valid_custom", TypeMath, "test", "test function", 1, 2, func(ctx *FunctionContext, args []any) (any, error) { return args[0], nil }) assert.NoError(t, err) - // 清理 + // Cleanup Unregister("valid_custom") } func TestFunctionAliasExecution(t *testing.T) { ctx := &FunctionContext{} - // 测试 pow 别名执行 + // Test pow alias execution result, err := Execute("pow", ctx, []any{2.0, 3.0}) if err != nil { t.Fatalf("pow execution failed: %v", err) @@ -880,7 +878,7 @@ func TestFunctionAliasExecution(t *testing.T) { t.Errorf("Expected 8.0, got %v", result) } - // 测试 len 别名执行 + // Test len alias execution result, err = Execute("len", ctx, []any{"hello"}) if err != nil { t.Fatalf("len execution failed: %v", err) @@ -889,7 +887,7 @@ func TestFunctionAliasExecution(t *testing.T) { t.Errorf("Expected 5, got %v", result) } - // 测试 ceil 别名执行 + // Test ceil alias execution result, err = Execute("ceil", ctx, []any{3.2}) if err != nil { t.Fatalf("ceil execution failed: %v", err) diff --git a/functions/functions_type.go b/functions/functions_type.go index 0aec96e..bc909a4 100644 --- a/functions/functions_type.go +++ b/functions/functions_type.go @@ -136,7 +136,7 @@ func (f *IsBoolFunction) Execute(ctx *FunctionContext, args []any) (any, error) return ok, nil } -// IsArrayFunction 检查是否为数组类型 +// IsArrayFunction checks whether it is an array type type IsArrayFunction struct { *BaseFunction } @@ -160,7 +160,7 @@ func (f *IsArrayFunction) Execute(ctx *FunctionContext, args []any) (any, error) return v.Kind() == reflect.Slice || v.Kind() == reflect.Array, nil } -// IsObjectFunction 检查是否为对象类型 +// IsObjectFunction checks whether it is an object type type IsObjectFunction struct { *BaseFunction } diff --git a/functions/functions_type_test.go b/functions/functions_type_test.go index fa32843..276aa11 100644 --- a/functions/functions_type_test.go +++ b/functions/functions_type_test.go @@ -4,7 +4,7 @@ import ( "testing" ) -// TestTypeFunctions 测试类型函数 +// TestTypeFunctions func TestTypeFunctions(t *testing.T) { tests := []struct { name string @@ -118,7 +118,7 @@ func TestTypeFunctions(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - // 验证参数 + // Validate parameters if err := tt.function.Validate(tt.args); err != nil { t.Errorf("Validate() error = %v", err) return @@ -136,7 +136,7 @@ func TestTypeFunctions(t *testing.T) { } } -// TestTypeFunctionValidation 测试类型函数的参数验证 +// TestTypeFunctionValidation: Validation of parameters for the test-type function func TestTypeFunctionValidation(t *testing.T) { tests := []struct { name string diff --git a/functions/functions_window.go b/functions/functions_window.go index 74a36cf..b3f223c 100644 --- a/functions/functions_window.go +++ b/functions/functions_window.go @@ -77,7 +77,7 @@ func (f *WindowEndFunction) Execute(ctx *FunctionContext, args []any) (any, erro return f.windowEnd, nil } -// 实现AggregatorFunction接口 +// Implement the AggregatorFunction interface func (f *WindowEndFunction) New() AggregatorFunction { return &WindowEndFunction{ BaseFunction: f.BaseFunction, @@ -85,7 +85,7 @@ func (f *WindowEndFunction) New() AggregatorFunction { } func (f *WindowEndFunction) Add(value any) { - // 窗口结束时间通常不需要累积计算 + // The window end time usually does not need to be cumulatively calculated f.windowEnd = value } @@ -104,7 +104,7 @@ func (f *WindowEndFunction) Clone() AggregatorFunction { } } -// ExpressionFunction 表达式函数,用于处理自定义表达式 +// ExpressionFunction is used to handle custom expressions type ExpressionFunction struct { *BaseFunction values []any @@ -122,14 +122,14 @@ func (f *ExpressionFunction) Validate(args []any) error { } func (f *ExpressionFunction) Execute(ctx *FunctionContext, args []any) (any, error) { - // 表达式函数的具体实现由表达式引擎处理 + // The specific implementation of expression functions is handled by the expression engine if len(args) == 0 { return nil, nil } return args[len(args)-1], nil } -// 实现AggregatorFunction接口 +// Implement the AggregatorFunction interface func (f *ExpressionFunction) New() AggregatorFunction { return &ExpressionFunction{ BaseFunction: f.BaseFunction, @@ -142,8 +142,8 @@ func (f *ExpressionFunction) Add(value any) { } func (f *ExpressionFunction) Result() any { - // 表达式聚合器的结果处理由表达式引擎处理 - // 这里只返回最后一个计算结果 + // The results of the expression aggregator are handled by the expression engine + // Here, only the last calculation result is returned if len(f.values) == 0 { return nil } @@ -163,7 +163,7 @@ func (f *ExpressionFunction) Clone() AggregatorFunction { return clone } -// ExpressionAggregatorFunction 表达式聚合器函数 - 用于处理非聚合函数在聚合查询中的情况 +// ExpressionAggregatorFunction Expression aggregator function - used to handle non-aggregated functions in aggregated queries type ExpressionAggregatorFunction struct { *BaseFunction lastResult any @@ -181,14 +181,14 @@ func (f *ExpressionAggregatorFunction) Validate(args []any) error { } func (f *ExpressionAggregatorFunction) Execute(ctx *FunctionContext, args []any) (any, error) { - // 对于表达式聚合器,直接返回最后一个值 + // For expression aggregators, the last value is returned directly if len(args) > 0 { return args[len(args)-1], nil } return nil, nil } -// 实现AggregatorFunction接口 +// Implement the AggregatorFunction interface func (f *ExpressionAggregatorFunction) New() AggregatorFunction { return &ExpressionAggregatorFunction{ BaseFunction: f.BaseFunction, @@ -197,15 +197,15 @@ func (f *ExpressionAggregatorFunction) New() AggregatorFunction { } func (f *ExpressionAggregatorFunction) Add(value any) { - // 对于表达式聚合器,保存最后一个计算结果 - // 表达式的计算结果应该是每个数据项的计算结果 + // For the expression aggregator, save the last calculation result + // The calculation result of the expression should be the result of each data item f.lastResult = value } func (f *ExpressionAggregatorFunction) Result() any { - // 对于表达式聚合器,返回最后一个计算结果 - // 注意:对于字符串函数如CONCAT,每个数据项都会产生一个结果 - // 在窗口聚合中,我们返回最后一个计算的结果 + // For the expression aggregator, return the last calculation result + // Note: For string functions like CONCAT, each data item will produce a result + // In window aggregation, we return the result of the last calculation return f.lastResult } @@ -220,7 +220,7 @@ func (f *ExpressionAggregatorFunction) Clone() AggregatorFunction { } } -// NthValueFunction 返回窗口中第N个值 +// NthValueFunction returns the Nth value in the window type NthValueFunction struct { *BaseFunction values []any @@ -231,7 +231,7 @@ func NewNthValueFunction() *NthValueFunction { return &NthValueFunction{ BaseFunction: NewBaseFunction("nth_value", TypeWindow, "窗口函数", "返回窗口中第N个值", 2, 2), values: make([]any, 0), - n: 1, // 默认第1个值 + n: 1, // Default is the first value } } @@ -240,7 +240,7 @@ func (f *NthValueFunction) Validate(args []any) error { return err } - // 验证N值 + // Verify the value of N n := 1 if nVal, ok := args[1].(int); ok { n = nVal @@ -254,7 +254,7 @@ func (f *NthValueFunction) Validate(args []any) error { return fmt.Errorf("nth_value n must be positive, got %d", n) } - // 设置n值 + // Set the value of n f.n = n return nil @@ -265,7 +265,7 @@ func (f *NthValueFunction) Execute(ctx *FunctionContext, args []any) (any, error return nil, err } - // 获取N值 + // Obtain the value of N n := 1 if nVal, ok := args[1].(int); ok { n = nVal @@ -279,7 +279,7 @@ func (f *NthValueFunction) Execute(ctx *FunctionContext, args []any) (any, error return nil, fmt.Errorf("nth_value n must be positive, got %d", n) } - // 返回第N个值(1-based索引) + // Returns the Nth value (1-based index) if len(f.values) >= n { return f.values[n-1], nil } @@ -287,12 +287,12 @@ func (f *NthValueFunction) Execute(ctx *FunctionContext, args []any) (any, error return nil, nil } -// 实现AggregatorFunction接口 +// Implement the AggregatorFunction interface func (f *NthValueFunction) New() AggregatorFunction { newInstance := &NthValueFunction{ BaseFunction: f.BaseFunction, values: make([]any, 0), - n: f.n, // 保持n参数 + n: f.n, // Maintain n parameters } return newInstance @@ -317,7 +317,7 @@ func (f *NthValueFunction) Clone() AggregatorFunction { clone := &NthValueFunction{ BaseFunction: f.BaseFunction, values: make([]any, len(f.values)), - n: f.n, // 保持n参数 + n: f.n, // Maintain n parameters } copy(clone.values, f.values) return clone diff --git a/functions/functions_window_test.go b/functions/functions_window_test.go index 3709a9b..fd46666 100644 --- a/functions/functions_window_test.go +++ b/functions/functions_window_test.go @@ -5,7 +5,7 @@ import ( "testing" ) -// isWindowFunction 判断是否为窗口函数 +// isWindowFunction to determine whether it is a window function func isWindowFunction(funcName string) bool { windowFunctions := map[string]bool{ "window_start": true, @@ -26,7 +26,7 @@ func TestNewWindowFunctions(t *testing.T) { wantErr bool setup func(fn AggregatorFunction) }{ - // first_value 函数测试 + // first_value Function Testing { name: "first_value basic", funcName: "first_value", @@ -48,7 +48,7 @@ func TestNewWindowFunctions(t *testing.T) { setup: func(fn AggregatorFunction) {}, }, - // last_value 函数测试 + // last_value Function testing { name: "last_value basic", funcName: "last_value", @@ -70,7 +70,7 @@ func TestNewWindowFunctions(t *testing.T) { setup: func(fn AggregatorFunction) {}, }, - // nth_value 函数测试 + // nth_value Function Testing { name: "nth_value first", funcName: "nth_value", @@ -136,41 +136,41 @@ func TestNewWindowFunctions(t *testing.T) { t.Fatalf("Function %s not found", tt.funcName) } - // 检查函数是否实现了AggregatorFunction接口 + // Check whether the function implements the AggregatorFunction interface aggFn, ok := fn.(AggregatorFunction) if !ok { t.Fatalf("Function %s does not implement AggregatorFunction", tt.funcName) } - // 先执行函数的Validate方法来设置参数 + // First, the function's Validate method is executed to set the parameters err := fn.Validate(tt.args) if (err != nil) != tt.wantErr { t.Errorf("Validate() error = %v, wantErr %v", err, tt.wantErr) return } - // 如果期望错误且Validate已经失败,则测试通过 + // If the expectation is incorrect and Validate has failed, the test passes if tt.wantErr && err != nil { return } - // 创建新的聚合器实例 + // Create a new aggregator instance aggInstance := aggFn.New() - // 执行setup函数添加测试数据 + // Execute the setup function to add test data if tt.setup != nil { tt.setup(aggInstance) } - // 对于窗口函数测试,不需要调用Execute方法 - // Execute方法主要用于流式处理,这里我们直接测试聚合器的Result方法 - // 如果需要测试Execute方法,应该在原始函数实例上调用 + // For window function testing, there is no need to call the Execute method + // The Execute method is mainly used for streaming processing. Here, we will directly test the aggregator's Result method + // If you need to test the Execute method, it should be called on the original function instance if !isWindowFunction(tt.funcName) { - // 对于非窗口函数,在聚合器实例上执行 + // For non-window functions, execute on the aggregator instance if aggFunc, ok := aggInstance.(Function); ok { _, err = aggFunc.Execute(nil, tt.args) } else { - // 执行函数 + // Execute the function _, err = fn.Execute(nil, tt.args) } } @@ -181,7 +181,7 @@ func TestNewWindowFunctions(t *testing.T) { } if !tt.wantErr { - // 对于窗口函数,我们主要测试聚合器的Result方法 + // For window functions, we mainly test the aggregator's Result method aggResult := aggInstance.Result() if tt.want != nil && aggResult != tt.want { t.Errorf("AggregatorFunction.Result() = %v, want %v", aggResult, tt.want) @@ -191,9 +191,9 @@ func TestNewWindowFunctions(t *testing.T) { } } -// 测试窗口函数的基本功能 +// Test the basic functions of the window function func TestWindowFunctionBasics(t *testing.T) { - // 测试window_start和window_end函数 + // Test window_start and window_end functions t.Run("WindowStartEndFunctions", func(t *testing.T) { windowStartFunc, exists := Get("window_start") if !exists { @@ -205,7 +205,7 @@ func TestWindowFunctionBasics(t *testing.T) { t.Fatal("window_end function not found") } - // 测试无窗口信息时的行为 + // Testing behavior when there is no window information ctx := &FunctionContext{ Data: map[string]any{}, } @@ -213,50 +213,50 @@ func TestWindowFunctionBasics(t *testing.T) { if err != nil { t.Errorf("Execute() error = %v", err) } - // 无窗口信息时应该返回nil或默认值 + // If there is no window information, nil or the default value should be returned _, err = windowEndFunc.Execute(ctx, []any{}) if err != nil { t.Errorf("Execute() error = %v", err) } - // 无窗口信息时应该返回nil或默认值 + // If there is no window information, nil or the default value should be returned }) } func TestWindowFunctionResetAndClone(t *testing.T) { - // 测试WindowStartFunction的Reset和Clone + // Test the reset and clone of WindowStartFunction windowStart := NewWindowStartFunction() agg := windowStart.New().(*WindowStartFunction) agg.Add("data1") agg.Add("data2") - // 测试Reset + // Test Reset agg.Reset() res := agg.Result() if res != nil { t.Errorf("WindowStart Reset failed, result = %v, want nil", res) } - // 测试Clone + // Testing Clone clone := agg.Clone().(*WindowStartFunction) if clone.windowStart != agg.windowStart { t.Errorf("WindowStart Clone failed") } - // 测试WindowEndFunction的Reset和Clone + // Test the reset and clone of WindowEndFunction windowEnd := NewWindowEndFunction() agg2 := windowEnd.New().(*WindowEndFunction) agg2.Add("data1") agg2.Add("data2") - // 测试Reset + // Test Reset agg2.Reset() res2 := agg2.Result() if res2 != nil { t.Errorf("WindowEnd Reset failed, result = %v, want nil", res2) } - // 测试Clone + // Testing Clone clone2 := agg2.Clone().(*WindowEndFunction) if clone2.windowEnd != agg2.windowEnd { t.Errorf("WindowEnd Clone failed") @@ -267,28 +267,28 @@ func TestExpressionFunction(t *testing.T) { fn := NewExpressionFunction() ctx := &FunctionContext{} - // 测试Execute方法 + // Test the Execute method _, err := fn.Execute(ctx, []any{"x + y", 1, 2}) if err != nil { t.Errorf("Execute error: %v", err) } - // 注意:这里的结果取决于表达式求值器的实现 - // 我们主要测试函数调用是否成功 + // Note: The result here depends on the implementation of the expression evaluator + // We mainly test whether the function call is successful - // 测试聚合器方法 + // Test aggregator methods agg := fn.New().(*ExpressionFunction) agg.Add("test") _ = agg.Result() - // 结果取决于表达式求值器的实现 + // The result depends on the implementation of the expression evaluator - // 测试Reset + // Test Reset agg.Reset() res := agg.Result() if res != nil { t.Errorf("Reset failed, result = %v, want nil", res) } - // 测试Clone + // Testing Clone clone := agg.Clone().(*ExpressionFunction) if !reflect.DeepEqual(clone.values, agg.values) { t.Errorf("Clone failed") @@ -299,27 +299,27 @@ func TestExpressionAggregatorFunction(t *testing.T) { fn := NewExpressionAggregatorFunction() ctx := &FunctionContext{} - // 测试Execute方法 + // Test the Execute method _, err := fn.Execute(ctx, []any{"sum(x)", 1, 2, 3}) if err != nil { t.Errorf("Execute error: %v", err) } - // 注意:这里的结果取决于表达式求值器的实现 + // Note: The result here depends on the implementation of the expression evaluator - // 测试聚合器方法 + // Test aggregator methods agg := fn.New().(*ExpressionAggregatorFunction) agg.Add("test") _ = agg.Result() - // 结果取决于表达式求值器的实现 + // The result depends on the implementation of the expression evaluator - // 测试Reset + // Test Reset agg.Reset() res := agg.Result() if res != nil { t.Errorf("Reset failed, result = %v, want nil", res) } - // 测试Clone + // Testing Clone clone := agg.Clone().(*ExpressionAggregatorFunction) if clone.lastResult != agg.lastResult { t.Errorf("Clone failed") @@ -329,7 +329,7 @@ func TestExpressionAggregatorFunction(t *testing.T) { func TestFirstValueFunction(t *testing.T) { fn := NewFirstValueFunction() - // 测试聚合器方法 + // Test aggregator methods agg := fn.New().(*FirstValueFunction) agg.Add("x") agg.Add("y") @@ -339,14 +339,14 @@ func TestFirstValueFunction(t *testing.T) { t.Errorf("Agg first_value result = %v, want x", res) } - // 测试Reset + // Test Reset agg.Reset() res2 := agg.Result() if res2 != nil { t.Errorf("Reset failed, result = %v, want nil", res2) } - // 测试Clone + // Testing Clone clone := agg.Clone().(*FirstValueFunction) if clone.firstValue != agg.firstValue { t.Errorf("Clone failed") @@ -356,7 +356,7 @@ func TestFirstValueFunction(t *testing.T) { func TestNthValueFunction(t *testing.T) { fn := NewNthValueFunction() - // 测试聚合器方法 + // Test aggregator methods agg := fn.New().(*NthValueFunction) agg.Add("x") agg.Add("y") @@ -366,14 +366,14 @@ func TestNthValueFunction(t *testing.T) { t.Errorf("Agg nth_value result = %v, want x", res) } - // 测试Reset + // Test Reset agg.Reset() res = agg.Result() if res != nil { t.Errorf("Reset failed, result = %v, want nil", res) } - // 测试Clone + // Testing Clone clone := agg.Clone().(*NthValueFunction) if clone.n != agg.n { t.Errorf("Clone failed") @@ -381,7 +381,7 @@ func TestNthValueFunction(t *testing.T) { } func TestWindowFunctionEdgeCases(t *testing.T) { - // NthValueFunction Validate/Execute边界 + // NthValueFunction Validate/Execute boundary nth := NewNthValueFunction() if err := nth.Validate([]any{}); err == nil { t.Error("NthValueFunction.Validate should fail for insufficient args") @@ -394,7 +394,7 @@ func TestWindowFunctionEdgeCases(t *testing.T) { agg2.Reset() _ = agg2.Clone() - // FirstValueFunction Validate/Execute边界 + // FirstValueFunction Validate/Execute boundary first := NewFirstValueFunction() if err := first.Validate([]any{}); err == nil { t.Error("FirstValueFunction.Validate should fail for insufficient args") @@ -407,7 +407,7 @@ func TestWindowFunctionEdgeCases(t *testing.T) { agg3.Reset() _ = agg3.Clone() - // LastValueFunction Validate/Execute边界 + // LastValueFunction Validate/Execute boundary last := NewLastValueFunction() if err := last.Validate([]any{}); err == nil { t.Error("LastValueFunction.Validate should fail for insufficient args") @@ -420,7 +420,7 @@ func TestWindowFunctionEdgeCases(t *testing.T) { agg4.Reset() _ = agg4.Clone() - // WindowStartFunction/WindowEndFunction Reset/Clone边界 + // WindowStartFunction/WindowEndFunction Reset/Clone boundary ws := NewWindowStartFunction().New().(*WindowStartFunction) ws.Reset() _ = ws.Clone() diff --git a/functions/init.go b/functions/init.go index 1db9be1..4f54555 100644 --- a/functions/init.go +++ b/functions/init.go @@ -1,6 +1,6 @@ package functions -// 初始化所有内置函数 +// Initialize all built-in functions func init() { registerBuiltinFunctions() } diff --git a/functions/integration_test.go b/functions/integration_test.go index 1c69143..81e77f7 100644 --- a/functions/integration_test.go +++ b/functions/integration_test.go @@ -5,12 +5,12 @@ import ( ) func TestFunctionsAggregatorIntegration(t *testing.T) { - // 测试聚合函数的增量计算 + // Test the incremental calculation of the aggregate function t.Run("SumAggregator", func(t *testing.T) { sumFunc := NewSumFunction() aggInstance := sumFunc.New() - // 测试增量计算 + // Test incremental calculations aggInstance.Add(10.0) aggInstance.Add(20.0) aggInstance.Add(30.0) @@ -82,7 +82,7 @@ func TestWindowFunctions(t *testing.T) { t.Run("WindowStartFunction", func(t *testing.T) { windowStartFunc := NewWindowStartFunction() - // 测试增量计算接口 + // Test incremental computing interface aggInstance := windowStartFunc.New() aggInstance.Add(1000) @@ -95,7 +95,7 @@ func TestWindowFunctions(t *testing.T) { t.Run("WindowEndFunction", func(t *testing.T) { windowEndFunc := NewWindowEndFunction() - // 测试增量计算接口 + // Test incremental computing interface aggInstance := windowEndFunc.New() aggInstance.Add(2000) @@ -118,7 +118,7 @@ func TestComplexAggregators(t *testing.T) { aggInstance.Add(5.0) result := aggInstance.Result() - // 标准差应该约为1.58 + // The standard deviation should be about 1.58 if result.(float64) < 1.5 || result.(float64) > 1.7 { t.Errorf("Expected stddev around 1.58, got %v", result) } @@ -169,9 +169,9 @@ func TestComplexAggregators(t *testing.T) { aggInstance.Add("a") aggInstance.Add("b") - aggInstance.Add("a") // 重复 + aggInstance.Add("a") // Repeat aggInstance.Add("c") - aggInstance.Add("b") // 重复 + aggInstance.Add("b") // Repeat result := aggInstance.Result() values, ok := result.([]any) diff --git a/functions/oneentry_register_test.go b/functions/oneentry_register_test.go index 719529e..1d372d8 100644 --- a/functions/oneentry_register_test.go +++ b/functions/oneentry_register_test.go @@ -7,8 +7,8 @@ import ( "github.com/stretchr/testify/assert" ) -// oneEntrySum 完整实现 functions.AggregatorFunction,用于验证: -// 自定义聚合函数只需 functions.Register 一个入口即可被运行期使用。 +// oneEntrySum fully implements functions.AggregatorFunction, used to verify: +// To customize an aggregate function, just use functions.Register can be used by runtime with just one entry point. type oneEntrySum struct { *BaseFunction sum float64 @@ -48,24 +48,24 @@ func (f *oneEntrySum) Result() any { } return f.sum } -func (f *oneEntrySum) Reset() { f.sum = 0; f.ok = false } +func (f *oneEntrySum) Reset() { f.sum = 0; f.ok = false } func (f *oneEntrySum) Clone() AggregatorFunction { return &oneEntrySum{BaseFunction: f.BaseFunction, sum: f.sum, ok: f.ok} } -// 验证主断言:自定义聚合函数只需 functions.Register 一个入口, -// 即可同时满足解析期校验与运行期聚合消费。 +// Verify the main assertion: Custom aggregation functions only need functions.Register a single entry point, +// This can simultaneously satisfy the aggregation consumption of parsing period validation and runtime periods. func TestAggregatorSingleEntryRegister(t *testing.T) { err := Register(newOneEntrySum()) assert.NoError(t, err) defer Unregister("zz_oneentry_sum") - // 解析期入口:function_validator.go 只查 functions.Get + // Resolution period entry: function_validator.go only checks functions.Get _, exists := Get("zz_oneentry_sum") assert.True(t, exists, "functions.Get 应能找到已注册的聚合函数") - // 运行期入口:聚合消费方走 CreateLegacyAggregator,由 registry.go 的自动 - // adapter 接通,无需手动 RegisterAggregatorAdapter / aggregator.Register。 + // Runtime entry: The aggregator consumer uses CreateLegacyAggregator, automated by registry.go + // adapter is turned on, no need to manually RegisterAggregatorAdapter / aggregator.Register. agg := CreateLegacyAggregator("zz_oneentry_sum") assert.NotNil(t, agg, "应通过自动 adapter 返回有效聚合器") @@ -75,9 +75,9 @@ func TestAggregatorSingleEntryRegister(t *testing.T) { assert.Equal(t, 6.0, agg.Result()) } -// legacyOnlySum 只实现 LegacyAggregatorFunction(3 方法接口)。 -// 注意:它与 oneEntrySum 不能合并,因为两套接口的 New() 返回类型不同 -// (AggregatorFunction vs LegacyAggregatorFunction),同一结构体无法同时实现。 +// legacyOnlySum only implements the LegacyAggregatorFunction (3 method interface). +// Note: It cannot be merged with oneEntrySum because the New() return types of the two interfaces are different +// (AggregatorFunction vs LegacyAggregatorFunction), the same structure cannot be implemented simultaneously. type legacyOnlySum struct { sum float64 ok bool @@ -100,8 +100,8 @@ func (f *legacyOnlySum) Result() any { return f.sum } -// 反证:仅走 aggregator.Register(RegisterLegacyAggregator)而不进 functions 表, -// SQL 解析期 functions.Get 找不到 —— 证明 aggregator.Register 单独不够。 +// Counterproof: Only go aggregator.Register(RegisterLegacyAggregator) without entering the functions table, +// SQL parsing phase functions.Get can't find — prove aggregator.Register alone is not enough. func TestLegacyOnlyRegisterNotVisibleToParser(t *testing.T) { RegisterLegacyAggregator("zz_legacy_only_sum", func() LegacyAggregatorFunction { return &legacyOnlySum{} @@ -115,29 +115,29 @@ type aliasedTestFunc struct { *BaseFunction } -func (f *aliasedTestFunc) Validate(args []any) error { return nil } +func (f *aliasedTestFunc) Validate(args []any) error { return nil } func (f *aliasedTestFunc) Execute(ctx *FunctionContext, args []any) (any, error) { return nil, nil } func newAliasedTestFunc(name string, aliases ...string) *aliasedTestFunc { return &aliasedTestFunc{BaseFunction: NewBaseFunctionWithAliases(name, TypeCustom, "test", "alias test", 0, 0, aliases)} } -// Register 别名原子化:别名冲突时主名不残留、已注册函数不受影响。 +// Register Alias Atomization: When there is an alias conflict, the primary name does not remain, and registered functions are not affected. func TestRegisterAliasAtomicNoDirtyState(t *testing.T) { a := newAliasedTestFunc("zz_alias_a", "zz_alias_shared") assert.NoError(t, Register(a)) defer Unregister("zz_alias_a") - // B 的别名与 A 冲突,注册应失败 + // B's alias conflicts with A, so registration should fail b := newAliasedTestFunc("zz_alias_b", "zz_alias_shared") err := Register(b) assert.Error(t, err) - // 不留脏:B 主名未进表 + // No Left Organs: B Main Name Not Submitted to the Table _, bExists := Get("zz_alias_b") assert.False(t, bExists, "别名冲突时主名不应残留") - // A 完整:主名与别名都在,且别名仍指向 A + // A Complete: Both the main name and the alias are present, and the alias still points to A _, aExists := Get("zz_alias_a") assert.True(t, aExists) got, sharedExists := Get("zz_alias_shared") @@ -145,7 +145,7 @@ func TestRegisterAliasAtomicNoDirtyState(t *testing.T) { assert.Equal(t, "zz_alias_a", got.GetName()) } -// RegisterCustomFunction 拒绝聚合/分析类型,避免 closure 静默废函数。 +// RegisterCustomFunction rejects aggregation/analysis types to avoid closure silence obsolete functions. func TestRegisterCustomFunctionRejectsAggregation(t *testing.T) { err := RegisterCustomFunction("zz_reject_agg", TypeAggregation, "test", "should reject", 1, 1, func(ctx *FunctionContext, args []any) (any, error) { return args[0], nil }) diff --git a/functions/registry.go b/functions/registry.go index 7a286c2..d7e0d2f 100644 --- a/functions/registry.go +++ b/functions/registry.go @@ -73,8 +73,8 @@ type FunctionRegistry struct { mu sync.RWMutex functions map[string]Function categories map[FunctionType][]Function - // snapshot 缓存 ListAll 的结果;Register/Unregister 后置 nil 失效,按需重建。 - // 函数集在 init 后基本稳定,避免每次 ListAll 都在全局锁下拷贝整张表。 + // snapshot caches the results of ListAll; Post-NIL of Register/Unregister fails and is rebuilt on demand. + // The function set is basically stable after init, preventing ListAll from copying the entire table under a global lock every time. snapshot map[string]Function } @@ -90,7 +90,7 @@ func NewFunctionRegistry() *FunctionRegistry { } // Register registers a function -// 注册函数及其别名到注册表中 +// Register functions and their aliases into the registry func (r *FunctionRegistry) Register(fn Function) error { r.mu.Lock() defer r.mu.Unlock() @@ -103,7 +103,7 @@ func (r *FunctionRegistry) Register(fn Function) error { name := strings.ToLower(fn.GetName()) aliases := fn.GetAliases() - // 先校验主名和所有别名都未占用,再统一写入,避免中途失败留下半注册状态。 + // First, check that the primary name and all aliases are not occupying, then write everything together to avoid failure and leaving a semi-registered state. if _, exists := r.functions[name]; exists { return fmt.Errorf("function %s already registered", name) } @@ -114,14 +114,14 @@ func (r *FunctionRegistry) Register(fn Function) error { } } - // 全部校验通过后统一写入主名与别名 + // After all checks are passed, the primary and alias are uniformly written to the primary and aliases r.functions[name] = fn for _, alias := range aliases { r.functions[strings.ToLower(alias)] = fn } r.categories[fn.GetType()] = append(r.categories[fn.GetType()], fn) - r.snapshot = nil // 失效 ListAll 快照 + r.snapshot = nil // Expired ListAll snapshot // Register aggregator adapter if fn.GetType() == TypeAggregation { _ = RegisterAggregatorAdapter(fn.GetName()) @@ -147,7 +147,7 @@ func (r *FunctionRegistry) GetByType(fnType FunctionType) []Function { } // ListAll lists all registered functions -// 返回缓存的只读快照(调用方不得修改);Register/Unregister 后失效、惰性重建。 +// Returns a read-only snapshot from the cache (the caller cannot be modified); Failure and inert reconstruction after Register/Unregister. func (r *FunctionRegistry) ListAll() map[string]Function { r.mu.RLock() if r.snapshot != nil { @@ -171,7 +171,7 @@ func (r *FunctionRegistry) ListAll() map[string]Function { } // Unregister removes a function -// 从注册表中移除函数及其所有别名 +// Remove functions and all aliases from the registry func (r *FunctionRegistry) Unregister(name string) bool { r.mu.Lock() defer r.mu.Unlock() @@ -182,10 +182,10 @@ func (r *FunctionRegistry) Unregister(name string) bool { return false } - // 删除主函数名 + // Delete the main function name delete(r.functions, strings.ToLower(fn.GetName())) - // 删除所有别名 + // Delete all aliases for _, alias := range fn.GetAliases() { delete(r.functions, strings.ToLower(alias)) } @@ -200,7 +200,7 @@ func (r *FunctionRegistry) Unregister(name string) bool { } } } - r.snapshot = nil // 失效 ListAll 快照 + r.snapshot = nil // Expired ListAll snapshot return true } @@ -243,8 +243,8 @@ func RegisterCustomFunction(name string, fnType FunctionType, category, descript if name == "" { return fmt.Errorf("function name cannot be empty") } - // 聚合/分析函数需实现 AggregatorFunction / StatefulAnalytic 接口,closure 形式无法满足, - // 注册后只会静默失效;请改为实现对应接口后用 functions.Register 注册。 + // The aggregation/analysis function requires implementing the AggregatorFunction / StatefulAnalytic interface; closure format cannot meet the requirements, + // After registration, it will only become silent and invalid; Please change it to implementing the corresponding interface and then use functions.Register. if fnType == TypeAggregation || fnType == TypeAnalytical { return fmt.Errorf("RegisterCustomFunction 不支持 %s 类型:聚合/分析函数请实现对应接口后用 Register 注册", fnType) } @@ -285,4 +285,4 @@ func (f *CustomFunction) Execute(ctx *FunctionContext, args []any) (any, error) return f.executor(ctx, args) } -// 内置函数注册见 builtin.go 的 registerBuiltinFunctions(由 init.go 的 init() 调用)。 +// For built-in function registration, see registerBuiltinFunctions in builtin.go (called by init() in init.go). diff --git a/functions/registry_test.go b/functions/registry_test.go index c69daca..0137754 100644 --- a/functions/registry_test.go +++ b/functions/registry_test.go @@ -8,7 +8,7 @@ import ( "github.com/stretchr/testify/assert" ) -// TestFunction 是用于测试的简单函数实现 +// TestFunction is a simple implementation of a function used for testing type TestFunction struct { name string fnType FunctionType @@ -45,13 +45,13 @@ func (f *TestFunction) Execute(ctx *FunctionContext, args []any) (any, error) { } func TestRegistryEdgeCases(t *testing.T) { - // 测试注册nil函数 + // Test the registration nil function t.Run("Register nil function", func(t *testing.T) { err := Register(nil) assert.Error(t, err) }) - // 测试注册重复函数名 + // Test registers duplicate function names t.Run("Register duplicate name", func(t *testing.T) { testFunc1 := &TestFunction{ name: "duplicate_func", @@ -68,37 +68,37 @@ func TestRegistryEdgeCases(t *testing.T) { maxArgs: -1, } - // 先注册第一个函数 + // First, register the first function err := Register(testFunc1) assert.NoError(t, err) - // 尝试注册同名函数 + // Try registering a function with the same name err = Register(testFunc2) assert.Error(t, err) assert.Contains(t, err.Error(), "already registered") - // 清理 + // Cleanup Unregister("duplicate_func") }) } -// TestGetFunctionEdgeCases 测试Get函数的边界情况 +// TestGetFunctionEdgeCases tests the boundary status of the Get function func TestGetFunctionEdgeCases(t *testing.T) { - // 测试获取不存在的函数 + // Test to retrieve a function that doesn't exist t.Run("Get non-existent function", func(t *testing.T) { func_, exists := Get("non_existent_func") assert.False(t, exists) assert.Nil(t, func_) }) - // 测试获取空名称函数 + // Test to get the empty name function t.Run("Get empty name function", func(t *testing.T) { func_, exists := Get("") assert.False(t, exists) assert.Nil(t, func_) }) - // 测试大小写不敏感性(函数名会被转换为小写) + // Testing case insensitivity (function names will be converted to lowercase) t.Run("Case insensitivity", func(t *testing.T) { testFunc := &TestFunction{ name: "lowercase_func", @@ -108,47 +108,47 @@ func TestGetFunctionEdgeCases(t *testing.T) { maxArgs: -1, } - // 注册小写函数名 + // Register lowercase function names err := Register(testFunc) assert.NoError(t, err) - // 用大写获取应该也能找到(因为内部转换为小写) + // You should be able to find it using uppercase (since internal conversion is lowercase). func_, exists := Get("LOWERCASE_FUNC") assert.True(t, exists) assert.NotNil(t, func_) - // 用正确的小写获取 + // Get with the correct lowercase func_, exists = Get("lowercase_func") assert.True(t, exists) assert.NotNil(t, func_) - // 清理 + // Cleanup Unregister("lowercase_func") }) } -// TestUnregisterEdgeCases 测试Unregister函数的边界情况 +// TestUnregisterEdgeCases tests the boundary status of the Unregister function func TestUnregisterEdgeCases(t *testing.T) { - // 测试注销不存在的函数 + // Test logout functions that do not exist t.Run("Unregister non-existent function", func(t *testing.T) { - // 这应该不会引起panic或错误 + // This should not cause panic or errors result := Unregister("non_existent_func") assert.False(t, result) - // 验证函数确实不存在 + // The verification function really does not exist func_, exists := Get("non_existent_func") assert.False(t, exists) assert.Nil(t, func_) }) - // 测试注销空名称函数 + // Test the logout empty name function t.Run("Unregister empty name", func(t *testing.T) { - // 这应该不会引起panic或错误 + // This should not cause panic or errors result := Unregister("") assert.False(t, result) }) - // 测试注销后再次注销 + // After test deregistration, deregister again t.Run("Double unregister", func(t *testing.T) { testFunc := &TestFunction{ name: "double_unregister_func", @@ -158,26 +158,26 @@ func TestUnregisterEdgeCases(t *testing.T) { maxArgs: -1, } - // 注册函数 + // Register the function err := Register(testFunc) assert.NoError(t, err) - // 第一次注销 + // The first time it was canceled result := Unregister("double_unregister_func") assert.True(t, result) func_, exists := Get("double_unregister_func") assert.False(t, exists) assert.Nil(t, func_) - // 第二次注销(应该不会有问题) + // Second cancellation (should be fine) result = Unregister("double_unregister_func") assert.False(t, result) }) } -// TestListAllEdgeCases 测试ListAll函数的边界情况 +// TestListAllEdgeCases tests the boundary status of the ListAll function func TestListAllEdgeCases(t *testing.T) { - // 注册一些测试函数 + // Register some test functions testFunctions := []*TestFunction{ { name: "test_func1", @@ -207,10 +207,10 @@ func TestListAllEdgeCases(t *testing.T) { assert.NoError(t, err) } - // 测试非空注册表 + // Test non-empty registry t.Run("Non-empty registry", func(t *testing.T) { functions := ListAll() - assert.GreaterOrEqual(t, len(functions), 3) // 至少包含我们注册的3个函数 + assert.GreaterOrEqual(t, len(functions), 3) // Include at least 3 functions we registered _, exists1 := functions["test_func1"] _, exists2 := functions["test_func2"] _, exists3 := functions["test_func3"] @@ -219,15 +219,15 @@ func TestListAllEdgeCases(t *testing.T) { assert.True(t, exists3) }) - // 清理测试函数 + // Clean up the test function for _, fn := range testFunctions { Unregister(fn.name) } } -// TestExecuteEdgeCases 测试Execute函数的边界情况 +// TestExecuteEdgeCases tests the boundary status of the Execute function func TestExecuteEdgeCases(t *testing.T) { - // 测试执行不存在的函数 + // Testing to execute a function that does not exist t.Run("Execute non-existent function", func(t *testing.T) { ctx := &FunctionContext{} result, err := Execute("non_existent_func", ctx, []any{}) @@ -236,7 +236,7 @@ func TestExecuteEdgeCases(t *testing.T) { assert.Contains(t, err.Error(), "not found") }) - // 测试执行返回错误的函数 + // The test executes the function that returns an error t.Run("Execute function that returns error", func(t *testing.T) { errorFunc := &TestFunction{ name: "error_func", @@ -258,11 +258,11 @@ func TestExecuteEdgeCases(t *testing.T) { assert.Nil(t, result) assert.Contains(t, err.Error(), "test error") - // 清理 + // Cleanup Unregister("error_func") }) - // 测试执行带参数的函数 + // Test executing a function with parameters t.Run("Execute function with arguments", func(t *testing.T) { sumFunc := &TestFunction{ name: "sum_func", @@ -289,21 +289,21 @@ func TestExecuteEdgeCases(t *testing.T) { assert.NoError(t, err) ctx := &FunctionContext{} - // 无参数调用 + // No parameter calls result, err := Execute("sum_func", ctx, []any{}) assert.NoError(t, err) assert.Equal(t, 0, result) - // 带参数调用 + // Call with parameters result, err = Execute("sum_func", ctx, []any{1, 2, 3, 4, 5}) assert.NoError(t, err) assert.Equal(t, 15, result) - // 清理 + // Cleanup Unregister("sum_func") }) - // 测试执行panic的函数 + // Test the function that executes panic t.Run("Execute function that panics", func(t *testing.T) { panicFunc := &TestFunction{ name: "panic_func", @@ -320,33 +320,33 @@ func TestExecuteEdgeCases(t *testing.T) { assert.NoError(t, err) ctx := &FunctionContext{} - // 执行应该捕获panic并返回错误 + // The execution should capture panic and return errors assert.Panics(t, func() { Execute("panic_func", ctx, []any{}) }) - // 清理 + // Cleanup Unregister("panic_func") }) } -// TestValidateEdgeCases 测试Validate函数的边界情况 +// TestValidateEdgeCases tests the boundaries of the Validate function func TestValidateEdgeCases(t *testing.T) { - // 测试验证不存在的函数 + // Testing verifies functions that do not exist t.Run("Validate non-existent function", func(t *testing.T) { err := Validate("non_existent_func") assert.Error(t, err) assert.Contains(t, err.Error(), "function 'non_existent_func' not found") }) - // 测试验证空名称函数 + // Test and verify the empty name function t.Run("Validate empty name function", func(t *testing.T) { err := Validate("") assert.Error(t, err) assert.Contains(t, err.Error(), "function '' not found") }) - // 测试验证有效函数 + // Test validates the effective function t.Run("Validate valid function", func(t *testing.T) { validFunc := &TestFunction{ name: "valid_func", @@ -365,11 +365,11 @@ func TestValidateEdgeCases(t *testing.T) { err = Validate("valid_func") assert.NoError(t, err) - // 清理 + // Cleanup Unregister("valid_func") }) - // 测试验证多个函数 + // Test and verify multiple functions t.Run("Validate multiple functions", func(t *testing.T) { testFunc1 := &TestFunction{ name: "test_func1", @@ -397,26 +397,26 @@ func TestValidateEdgeCases(t *testing.T) { err = Register(testFunc2) assert.NoError(t, err) - // 验证存在的函数 + // Verify the function that exists err = Validate("test_func1") assert.NoError(t, err) err = Validate("test_func2") assert.NoError(t, err) - // 验证不存在的函数 + // Verify functions that do not exist err = Validate("test_func3") assert.Error(t, err) - // 清理 + // Cleanup Unregister("test_func1") Unregister("test_func2") }) } -// TestConcurrentAccess 测试并发访问 +// TestConcurrentAccess tests for concurrent access func TestConcurrentAccess(t *testing.T) { - // 这个测试检查注册表在并发访问时的行为 - // 注意:实际的并发安全需要在registry实现中处理 + // This test checks the behavior of the registry during concurrent access + // Note: Actual concurrency security needs to be handled in the registry implementation t.Run("Concurrent register and get", func(t *testing.T) { testFunc := &TestFunction{ name: "concurrent_func", @@ -429,29 +429,29 @@ func TestConcurrentAccess(t *testing.T) { }, } - // 注册函数 + // Register the function err := Register(testFunc) assert.NoError(t, err) - // 获取函数 + // Get the function func_, exists := Get("concurrent_func") assert.True(t, exists) assert.NotNil(t, func_) ctx := &FunctionContext{} - // 执行函数 + // Execute the function result, err := Execute("concurrent_func", ctx, []any{}) assert.NoError(t, err) assert.Equal(t, "concurrent_test", result) - // 清理 + // Cleanup Unregister("concurrent_func") }) } -// TestFunctionSignatureVariations 测试不同函数签名的变化 +// TestFunctionSignatureVariations tests changes in signatures of different functions func TestFunctionSignatureVariations(t *testing.T) { - // 测试无参数函数 + // Testing the no-parameter function t.Run("No parameter function", func(t *testing.T) { noParamFunc := &TestFunction{ name: "no_param_func", @@ -475,11 +475,11 @@ func TestFunctionSignatureVariations(t *testing.T) { assert.NoError(t, err) assert.Equal(t, "no_params", result) - // 清理 + // Cleanup Unregister("no_param_func") }) - // 测试可变参数函数 + // Test variable parameter functions t.Run("Variadic parameter function", func(t *testing.T) { variadicFunc := &TestFunction{ name: "variadic_func", @@ -496,7 +496,7 @@ func TestFunctionSignatureVariations(t *testing.T) { assert.NoError(t, err) ctx := &FunctionContext{} - // 测试不同数量的参数 + // Test different numbers of parameters result, err := Execute("variadic_func", ctx, []any{}) assert.NoError(t, err) assert.Equal(t, 0, result) @@ -509,13 +509,13 @@ func TestFunctionSignatureVariations(t *testing.T) { assert.NoError(t, err) assert.Equal(t, 3, result) - // 清理 + // Cleanup Unregister("variadic_func") }) - // 测试返回不同类型的函数 + // Tests return functions of different types t.Run("Different return types", func(t *testing.T) { - // 返回字符串 + // Returns the string stringFunc := &TestFunction{ name: "string_func", fnType: TypeCustom, @@ -527,7 +527,7 @@ func TestFunctionSignatureVariations(t *testing.T) { }, } - // 返回数字 + // Return the numbers numberFunc := &TestFunction{ name: "number_func", fnType: TypeCustom, @@ -539,7 +539,7 @@ func TestFunctionSignatureVariations(t *testing.T) { }, } - // 返回布尔值 + // Returns the boolean value boolFunc := &TestFunction{ name: "bool_func", fnType: TypeCustom, @@ -551,7 +551,7 @@ func TestFunctionSignatureVariations(t *testing.T) { }, } - // 返回nil + // Return nil nilFunc := &TestFunction{ name: "nil_func", fnType: TypeCustom, @@ -563,14 +563,14 @@ func TestFunctionSignatureVariations(t *testing.T) { }, } - // 注册所有函数 + // Register all functions assert.NoError(t, Register(stringFunc)) assert.NoError(t, Register(numberFunc)) assert.NoError(t, Register(boolFunc)) assert.NoError(t, Register(nilFunc)) ctx := &FunctionContext{} - // 测试执行 + // Test execution result, err := Execute("string_func", ctx, []any{}) assert.NoError(t, err) assert.Equal(t, "string_result", result) @@ -587,7 +587,7 @@ func TestFunctionSignatureVariations(t *testing.T) { assert.NoError(t, err) assert.Nil(t, result) - // 清理 + // Cleanup Unregister("string_func") Unregister("number_func") Unregister("bool_func") diff --git a/functions/regression_test.go b/functions/regression_test.go index 6ae86a9..f6a1beb 100644 --- a/functions/regression_test.go +++ b/functions/regression_test.go @@ -13,13 +13,13 @@ func TestSubstringMultibyte(t *testing.T) { t.Fatal("substring not found") } - // 你好世界 = runes [你 好 世 界]; substring(s, 1, 2) -> "好世". + // Hello World = runes [Hello World]; substring(s, 1, 2) -> "Good World". got, err := fn.Execute(nil, []any{"你好世界", int64(1), int64(2)}) if err != nil { t.Fatalf("unexpected error: %v", err) } if got != "好世" { - t.Errorf("substring(你好世界,1,2) = %q, want %q", got, "好世") + t.Errorf("substring(Hello World, 1,2) = %q, want %q", got, "Age-loving") } // Two-arg form from the middle. @@ -28,7 +28,7 @@ func TestSubstringMultibyte(t *testing.T) { t.Fatalf("unexpected error: %v", err) } if got != "世界" { - t.Errorf("substring(你好世界,2) = %q, want %q", got, "世界") + t.Errorf("substring (Hello World, 2) = %q, want %q", got, "World") } // ASCII still works (rune count == byte count). diff --git a/go.mod b/go.mod index d90340a..ec6e2ee 100644 --- a/go.mod +++ b/go.mod @@ -1,10 +1,10 @@ module github.com/rulego/streamsql -go 1.18 +go 1.26.5 require ( github.com/expr-lang/expr v1.17.8 - github.com/stretchr/testify v1.10.0 + github.com/stretchr/testify v1.11.1 ) require ( diff --git a/go.sum b/go.sum index 26c614b..385ffb3 100644 --- a/go.sum +++ b/go.sum @@ -4,8 +4,8 @@ github.com/expr-lang/expr v1.17.8 h1:W1loDTT+0PQf5YteHSTpju2qfUfNoBt4yw9+wOEU9VM github.com/expr-lang/expr v1.17.8/go.mod h1:8/vRC7+7HBzESEqt5kKpYXxrxkr31SaO8r40VO/1IT4= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= -github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= diff --git a/logger/logger_test.go b/logger/logger_test.go index 085ee7c..c95ff1c 100644 --- a/logger/logger_test.go +++ b/logger/logger_test.go @@ -23,7 +23,7 @@ import ( "testing" ) -// TestLevel_String 测试日志级别的字符串表示 +// TestLevel_String String representation at the test log level func TestLevel_String(t *testing.T) { tests := []struct { level Level @@ -34,7 +34,7 @@ func TestLevel_String(t *testing.T) { {WARN, "WARN"}, {ERROR, "ERROR"}, {OFF, "OFF"}, - {Level(999), "UNKNOWN"}, // 测试未知级别 + {Level(999), "UNKNOWN"}, // Testing unknown levels } for _, test := range tests { @@ -44,7 +44,7 @@ func TestLevel_String(t *testing.T) { } } -// TestNewLogger 测试创建新的日志器 +// TestNewLogger tests the creation of a new logger func TestNewLogger(t *testing.T) { var buf bytes.Buffer logger := NewLogger(INFO, &buf) @@ -53,7 +53,7 @@ func TestNewLogger(t *testing.T) { t.Fatal("NewLogger() returned nil") } - // 测试日志输出 + // Test log output logger.Info("test message") output := buf.String() @@ -66,7 +66,7 @@ func TestNewLogger(t *testing.T) { } } -// TestDefaultLogger_Debug 测试调试级别日志 +// TestDefaultLogger_Debug Test debug level logs func TestDefaultLogger_Debug(t *testing.T) { var buf bytes.Buffer logger := NewLogger(DEBUG, &buf) @@ -83,7 +83,7 @@ func TestDefaultLogger_Debug(t *testing.T) { } } -// TestDefaultLogger_Info 测试信息级别日志 +// TestDefaultLogger_Info Test information level log func TestDefaultLogger_Info(t *testing.T) { var buf bytes.Buffer logger := NewLogger(INFO, &buf) @@ -100,7 +100,7 @@ func TestDefaultLogger_Info(t *testing.T) { } } -// TestDefaultLogger_Warn 测试警告级别日志 +// TestDefaultLogger_Warn Test warning level logs func TestDefaultLogger_Warn(t *testing.T) { var buf bytes.Buffer logger := NewLogger(WARN, &buf) @@ -117,7 +117,7 @@ func TestDefaultLogger_Warn(t *testing.T) { } } -// TestDefaultLogger_Error 测试错误级别日志 +// TestDefaultLogger_Error Test error level logs func TestDefaultLogger_Error(t *testing.T) { var buf bytes.Buffer logger := NewLogger(ERROR, &buf) @@ -134,15 +134,15 @@ func TestDefaultLogger_Error(t *testing.T) { } } -// TestDefaultLogger_SetLevel 测试设置日志级别 +// TestDefaultLogger_SetLevel Test the log level func TestDefaultLogger_SetLevel(t *testing.T) { var buf bytes.Buffer logger := NewLogger(DEBUG, &buf) - // 设置为 ERROR 级别 + // Set to ERROR level logger.SetLevel(ERROR) - // 测试低级别日志不会输出 + // Low-level test logs do not output logger.Debug("debug message") logger.Info("info message") logger.Warn("warn message") @@ -152,7 +152,7 @@ func TestDefaultLogger_SetLevel(t *testing.T) { t.Errorf("Expected no output for lower level logs, got: %s", output) } - // 测试 ERROR 级别日志会输出 + // Test ERROR level logs will be output buf.Reset() logger.Error("error message") output = buf.String() @@ -162,7 +162,7 @@ func TestDefaultLogger_SetLevel(t *testing.T) { } } -// TestDefaultLogger_LevelFiltering 测试日志级别过滤 +// TestDefaultLogger_LevelFiltering Test log-level filtering func TestDefaultLogger_LevelFiltering(t *testing.T) { tests := []struct { loggerLevel Level @@ -192,7 +192,7 @@ func TestDefaultLogger_LevelFiltering(t *testing.T) { var buf bytes.Buffer logger := NewLogger(test.loggerLevel, &buf) - // 根据消息级别调用相应的日志方法 + // Call the corresponding logging method according to the message level switch test.messageLevel { case DEBUG: logger.Debug("test message") @@ -214,7 +214,7 @@ func TestDefaultLogger_LevelFiltering(t *testing.T) { } } -// TestDefaultLogger_OFFLevel 测试 OFF 级别 +// TestDefaultLogger_OFFLevel Test OFF level func TestDefaultLogger_OFFLevel(t *testing.T) { var buf bytes.Buffer logger := NewLogger(OFF, &buf) @@ -230,7 +230,7 @@ func TestDefaultLogger_OFFLevel(t *testing.T) { } } -// TestNewDiscardLogger 测试丢弃日志器 +// TestNewDiscardLogger tests the drop logger func TestNewDiscardLogger(t *testing.T) { logger := NewDiscardLogger() @@ -238,33 +238,33 @@ func TestNewDiscardLogger(t *testing.T) { t.Fatal("NewDiscardLogger() returned nil") } - // 测试所有方法都不会产生输出或错误 + // Testing all methods does not produce output or errors logger.Debug("debug message") logger.Info("info message") logger.Warn("warn message") logger.Error("error message") logger.SetLevel(DEBUG) - // 如果没有 panic,测试通过 + // If there is no panic, the test passes } -// TestGlobalLogger 测试全局日志器 +// TestGlobalLogger tests the global logger func TestGlobalLogger(t *testing.T) { - // 保存原始的全局日志器 + // Preserves the original global logger original := GetDefault() defer SetDefault(original) - // 创建测试用的日志器 + // Create a test logger var buf bytes.Buffer testLogger := NewLogger(DEBUG, &buf) SetDefault(testLogger) - // 测试全局日志器是否被正确设置 + // Test whether the global logger is set correctly if GetDefault() != testLogger { t.Error("Global logger was not set correctly") } - // 测试全局日志方法 + // Test the global logging method Debug("global debug message") Info("global info message") Warn("global warn message") @@ -286,7 +286,7 @@ func TestGlobalLogger(t *testing.T) { } } -// TestLogFormat 测试日志格式 +// TestLogFormat: Test log format func TestLogFormat(t *testing.T) { var buf bytes.Buffer logger := NewLogger(INFO, &buf) @@ -294,23 +294,23 @@ func TestLogFormat(t *testing.T) { logger.Info("test message") output := buf.String() - // 检查时间戳格式 (YYYY-MM-DD HH:MM:SS.mmm) + // Check the timestamp format (YYYY-MM-DD HH:MM:SS.mmm) if !strings.Contains(output, "[") || !strings.Contains(output, "]") { t.Errorf("Expected timestamp format in brackets, got: %s", output) } - // 检查日志级别格式 + // Check the log-level format if !strings.Contains(output, "[INFO]") { t.Errorf("Expected [INFO] in output, got: %s", output) } - // 检查消息内容 + // Check the message content if !strings.Contains(output, "test message") { t.Errorf("Expected 'test message' in output, got: %s", output) } } -// TestLoggerWithStdout 测试使用标准输出的日志器 +// TestLoggerWithStdout tests loggers using standard output func TestLoggerWithStdout(t *testing.T) { logger := NewLogger(INFO, os.Stdout) @@ -318,11 +318,11 @@ func TestLoggerWithStdout(t *testing.T) { t.Fatal("NewLogger() with os.Stdout returned nil") } - // 这个测试主要确保不会 panic + // This test mainly ensures there is no panic logger.Info("test message to stdout") } -// TestLoggerWithStderr 测试使用标准错误的日志器 +// TestLoggerWithStderr tests using a standard error logger func TestLoggerWithStderr(t *testing.T) { logger := NewLogger(ERROR, os.Stderr) @@ -330,18 +330,18 @@ func TestLoggerWithStderr(t *testing.T) { t.Fatal("NewLogger() with os.Stderr returned nil") } - // 这个测试主要确保不会 panic + // This test mainly ensures there is no panic logger.Error("test error message to stderr") } -// TestConcurrentLogging 测试并发日志记录 +// TestConcurrentLogging tests concurrent logging func TestConcurrentLogging(t *testing.T) { var buf bytes.Buffer logger := NewLogger(INFO, &buf) done := make(chan bool, 10) - // 启动多个 goroutine 并发写日志 + // Start multiple goroutines and write logs for i := 0; i < 10; i++ { go func(id int) { logger.Info("concurrent message from goroutine %d", id) @@ -349,26 +349,26 @@ func TestConcurrentLogging(t *testing.T) { }(i) } - // 等待所有 goroutine 完成 + // Wait for all goroutines to complete for i := 0; i < 10; i++ { <-done } output := buf.String() - // 检查是否有输出(具体内容可能因并发而乱序) + // Check for outputs (specific content may be disordered due to concurrency) if len(output) == 0 { t.Error("Expected some output from concurrent logging") } - // 检查是否包含预期的消息数量 + // Check whether the expected number of messages is included messageCount := strings.Count(output, "concurrent message") if messageCount != 10 { t.Errorf("Expected 10 concurrent messages, got %d", messageCount) } } -// TestLoggerParameterFormatting 测试日志参数格式化 +// TestLoggerParameterFormatting: Formatting test log parameters func TestLoggerParameterFormatting(t *testing.T) { var buf bytes.Buffer logger := NewLogger(DEBUG, &buf) @@ -396,7 +396,7 @@ func TestLoggerParameterFormatting(t *testing.T) { } } -// TestDefaultLoggerInitialization 测试默认日志器初始化 +// TestDefaultLoggerInitialization Tests default logger initialization func TestDefaultLoggerInitialization(t *testing.T) { defaultLogger := GetDefault() @@ -404,70 +404,70 @@ func TestDefaultLoggerInitialization(t *testing.T) { t.Fatal("Default logger should not be nil") } - // 测试默认日志器可以正常工作 + // Test that the default logger works properly defaultLogger.Info("test default logger") } -// TestLoggerInternalLogMethod 测试内部 log 方法的 OFF 级别处理 +// TestLoggerInternalLogMethod tests the OFF level handling of internal log methods func TestLoggerInternalLogMethod(t *testing.T) { var buf bytes.Buffer logger := NewLogger(DEBUG, &buf).(*defaultLogger) - // 设置为 OFF 级别 + // Set to OFF level logger.SetLevel(OFF) - // 直接调用内部 log 方法 + // Directly call the internal log method logger.log(ERROR, "test message") - // 验证没有输出 + // Verification has no output output := buf.String() if len(output) > 0 { t.Errorf("Expected no output when level is OFF, got: %s", output) } } -// TestDiscardLoggerAllMethods 测试丢弃日志器的所有方法 +// TestDiscardLoggerAllMethods tests all methods for discarding loggers func TestDiscardLoggerAllMethods(t *testing.T) { logger := NewDiscardLogger() - // 测试所有级别的日志方法 + // Test logging methods at all levels logger.Debug("debug %s", "test") logger.Info("info %d", 123) logger.Warn("warn %v", true) logger.Error("error %s %d", "test", 456) - // 测试设置级别 + // Test the level settings logger.SetLevel(DEBUG) logger.SetLevel(INFO) logger.SetLevel(WARN) logger.SetLevel(ERROR) logger.SetLevel(OFF) - // 如果没有 panic 或错误,测试通过 + // If there is no panic or error, the test passes } -// TestLoggerWithNilArgs 测试使用 nil 参数的日志记录 +// TestLoggerWithNilArgs tests logging using nil parameters func TestLoggerWithNilArgs(t *testing.T) { var buf bytes.Buffer logger := NewLogger(DEBUG, &buf) - // 测试没有参数的情况 + // Testing without parameters logger.Info("message without args") output := buf.String() if !strings.Contains(output, "message without args") { t.Errorf("Expected message in output, got: %s", output) } - // 测试空参数列表 + // Test null parameter list buf.Reset() - logger.Info("message with empty args", []any{}...) + logger.Info("message with empty args") output = buf.String() if !strings.Contains(output, "message with empty args") { t.Errorf("Expected message in output, got: %s", output) } } -// TestLoggerTimestampFormat 测试时间戳格式 +// TestLoggerTimestampFormat: Test the timestamp format func TestLoggerTimestampFormat(t *testing.T) { var buf bytes.Buffer logger := NewLogger(INFO, &buf) @@ -475,50 +475,50 @@ func TestLoggerTimestampFormat(t *testing.T) { logger.Info("timestamp test") output := buf.String() - // 检查时间戳格式:[YYYY-MM-DD HH:MM:SS.mmm] - // 使用正则表达式验证时间戳格式 + // Check timestamp format: [YYYY-MM-DD HH:MM:SS.mmm] + // Use regular expressions to verify the timestamp format lines := strings.Split(strings.TrimSpace(output), "\n") if len(lines) == 0 { t.Fatal("No output lines found") } line := lines[0] - // 检查是否包含日期时间格式 + // Check whether the date and time format is included if !strings.Contains(line, "[") || !strings.Contains(line, "]") { t.Errorf("Expected timestamp in brackets, got: %s", line) } - // 检查是否包含年份(简单验证) + // Check if the year is included (simple verification) if !strings.Contains(line, "2025") && !strings.Contains(line, "2024") && !strings.Contains(line, "2026") { t.Errorf("Expected year in timestamp, got: %s", line) } } -// TestGlobalLoggerRestore 测试全局日志器的恢复 +// TestGlobalLoggerRestore tests the recovery of the global logger func TestGlobalLoggerRestore(t *testing.T) { - // 保存原始的全局日志器 + // Preserves the original global logger original := GetDefault() - // 创建新的测试日志器 + // Create a new test logger var buf bytes.Buffer testLogger := NewLogger(ERROR, &buf) SetDefault(testLogger) - // 验证设置成功 + // Verify that the setup is successful if GetDefault() != testLogger { t.Error("Failed to set test logger") } - // 恢复原始日志器 + // Restore the original logger SetDefault(original) - // 验证恢复成功 + // Verification of successful recovery if GetDefault() != original { t.Error("Failed to restore original logger") } } -// TestLevelConstants 测试所有日志级别常量 +// TestLevelConstants tests all log-level constants func TestLevelConstants(t *testing.T) { expectedLevels := map[Level]string{ DEBUG: "DEBUG", @@ -534,7 +534,7 @@ func TestLevelConstants(t *testing.T) { } } - // 测试级别的数值 + // Test level values if DEBUG != 0 { t.Errorf("DEBUG should be 0, got %d", DEBUG) } diff --git a/options_test.go b/options_test.go index 978a5d3..dc53082 100644 --- a/options_test.go +++ b/options_test.go @@ -29,12 +29,12 @@ import ( "github.com/stretchr/testify/require" ) -// TestWithLogLevel 测试日志级别设置选项 +// TestWithLogLevel Option for setting the test log level func TestWithLogLevel(t *testing.T) { t.Run("设置Debug级别", func(t *testing.T) { s := New(WithLogLevel(logger.DEBUG)) - // 验证选项函数执行成功(通过检查没有panic来验证) + // Verify that the option function executed successfully (verified by checking for no panic) assert.NotNil(t, s) }) @@ -51,22 +51,22 @@ func TestWithLogLevel(t *testing.T) { }) } -// TestWithDiscardLog 测试禁用日志输出选项 +// TestWithDiscardLog tests disabling log output options func TestWithDiscardLog(t *testing.T) { t.Run("禁用日志输出", func(t *testing.T) { s := New(WithDiscardLog()) - // 验证日志器是否被设置为丢弃日志器 - // 这里我们检查日志器的类型或行为 + // Verify whether the logger is set to drop logger + // Here, we examine the type or behavior of the logger loggerInstance := logger.GetDefault() assert.NotNil(t, loggerInstance) - // 验证选项函数执行成功 + // Verify that the option function executed successfully assert.NotNil(t, s) }) } -// TestWithHighPerformance 测试高性能配置选项 +// TestWithHighPerformance tests high-performance configuration options func TestWithHighPerformance(t *testing.T) { t.Run("设置高性能模式", func(t *testing.T) { s := New(WithHighPerformance()) @@ -75,7 +75,7 @@ func TestWithHighPerformance(t *testing.T) { }) } -// TestWithLowLatency 测试低延迟配置选项 +// TestWithLowLatency tests low-latency configuration options func TestWithLowLatency(t *testing.T) { t.Run("设置低延迟模式", func(t *testing.T) { s := New(WithLowLatency()) @@ -84,7 +84,7 @@ func TestWithLowLatency(t *testing.T) { }) } -// TestWithCustomPerformance 测试自定义性能配置选项 +// TestWithCustomPerformance Tests custom performance configuration options func TestWithCustomPerformance(t *testing.T) { t.Run("设置自定义性能配置", func(t *testing.T) { customConfig := types.PerformanceConfig{ @@ -109,7 +109,7 @@ func TestWithCustomPerformance(t *testing.T) { }) } -// TestWithBufferSizes 测试缓冲区大小配置选项 +// TestWithBufferSizes tests the buffer size configuration option func TestWithBufferSizes(t *testing.T) { t.Run("设置自定义缓冲区大小", func(t *testing.T) { dataChannelSize := 1500 @@ -136,7 +136,7 @@ func TestWithBufferSizes(t *testing.T) { }) } -// TestWithOverflowStrategy 测试溢出策略配置选项 +// TestWithOverflowStrategy: Tests the overflow policy configuration options func TestWithOverflowStrategy(t *testing.T) { t.Run("设置drop策略", func(t *testing.T) { strategy := "drop" @@ -178,7 +178,7 @@ func TestWithOverflowStrategy(t *testing.T) { }) } -// TestWithWorkerConfig 测试工作池配置选项 +// TestWithWorkerConfig Test the workspace configuration options func TestWithWorkerConfig(t *testing.T) { t.Run("设置自定义工作池配置", func(t *testing.T) { sinkPoolSize := 10 @@ -205,7 +205,7 @@ func TestWithWorkerConfig(t *testing.T) { }) } -// TestWithMonitoring 测试监控配置选项 +// TestWithMonitoring: Test monitoring configuration options func TestWithMonitoring(t *testing.T) { t.Run("启用详细监控", func(t *testing.T) { updateInterval := 5 * time.Second @@ -234,10 +234,10 @@ func TestWithMonitoring(t *testing.T) { }) } -// TestOptionsCombination 测试选项组合使用 +// TestOptionsCombination is used for test option combinations func TestOptionsCombination(t *testing.T) { t.Run("组合多个选项", func(t *testing.T) { - // 应用多个选项 + // Apply multiple options s := New( WithHighPerformance(), WithBufferSizes(3000, 400, 150), @@ -245,30 +245,30 @@ func TestOptionsCombination(t *testing.T) { WithMonitoring(10*time.Second, true), ) - // 验证最后应用的选项生效(WithMonitoring是最后一个,会覆盖前面的自定义配置) + // Verify that the last applied option is effective (WithMonitoring is the last and will override the previous custom configuration). assert.Equal(t, "custom", s.performanceMode) assert.NotNil(t, s.customConfig) - // 由于WithMonitoring是最后应用的,它会重置配置为默认值,然后只设置监控相关配置 - assert.Equal(t, 1000, s.customConfig.BufferConfig.DataChannelSize) // 默认值 - assert.Equal(t, "drop", s.customConfig.OverflowConfig.Strategy) // 默认值 + // Since WithMonitoring is the last to be applied, it resets the configuration to the default value and only sets the monitoring-related configuration + assert.Equal(t, 1000, s.customConfig.BufferConfig.DataChannelSize) // Default values + assert.Equal(t, "drop", s.customConfig.OverflowConfig.Strategy) // Default values assert.True(t, s.customConfig.MonitoringConfig.EnableMonitoring) }) t.Run("预设模式后应用自定义选项", func(t *testing.T) { - // 先设置预设模式,再应用自定义选项 + // Set the preset mode first, then apply custom options s := New( WithLowLatency(), WithBufferSizes(500, 60, 25), ) - // 验证最后应用的选项生效(custom模式覆盖low_latency模式) + // Verify that the last applied option is effective (custom mode overrides low_latency mode) assert.Equal(t, "custom", s.performanceMode) assert.NotNil(t, s.customConfig) assert.Equal(t, 500, s.customConfig.BufferConfig.DataChannelSize) }) } -// TestOptionsEdgeCases 测试选项边界情况 +// TestOptionsEdgeCases tests the option boundary status func TestOptionsEdgeCases(t *testing.T) { t.Run("空字符串策略", func(t *testing.T) { s := New(WithOverflowStrategy("", 0)) @@ -318,7 +318,7 @@ func (c *captureLogger) record(level, format string, args ...any) { c.mu.Unlock() } -// TestWithLogger 验证 WithLogger 按实例路由引擎日志(实例间不串扰)。 +// TestWithLogger verifies that WithLogger routes engine logs by instance (no crosstalk between instances). func TestWithLogger(t *testing.T) { sch := schema.Schema{Fields: []schema.FieldDef{{Name: "v", Type: schema.TypeInt, Required: true}}} diff --git a/rsql/analytic_extract.go b/rsql/analytic_extract.go index b9f1f0c..ac4c06b 100644 --- a/rsql/analytic_extract.go +++ b/rsql/analytic_extract.go @@ -9,11 +9,12 @@ import ( "github.com/rulego/streamsql/types" ) -// extractWhereAnalyticCalls 从 WHERE 文本中提取分析函数调用(含可选 OVER),替换为占位符。 -// 返回改写后的 WHERE(占位符引用)和调用信息列表。无分析函数时原样返回。 +// extractWhereAnalyticCalls extracts analysis function calls (including optional OVER) from the WHERE text and replaces them with placeholders. +// Returns the rewritten WHERE (placeholder reference) and a list of call information. Returns as is when there is no analysis function. // -// 例:"current > 300 and lag(current) over (partition by deviceId) < 300" -// → "current > 300 and __analytic_0__ < 300" + [{Placeholder:__analytic_0__, FuncName:lag, Over:{PartitionBy:[deviceId]}}] +// Example: "current > 300 and lag(current) over (partition by deviceId) < 300" +// +// → "current > 300 and __analytic_0__ < 300" + [{Placeholder:__analytic_0__, FuncName:lag, Over:{PartitionBy:[deviceId]}}] func extractWhereAnalyticCalls(condition string) (string, []types.WhereAnalyticCall, error) { if strings.TrimSpace(condition) == "" { return condition, nil, nil @@ -31,7 +32,7 @@ func extractWhereAnalyticCalls(condition string) (string, []types.WhereAnalyticC } ident := condition[start:i] lowerIdent := strings.ToLower(ident) - // 命中分析函数 + 后跟 ( + // Hit analysis function + heel ( if fn, ok := functions.Get(lowerIdent); ok && fn.GetType() == functions.TypeAnalytical { j := skipSpaces(condition, i) if j < len(condition) && condition[j] == '(' { @@ -79,25 +80,25 @@ func extractWhereAnalyticCalls(condition string) (string, []types.WhereAnalyticC i++ } result := out.String() - // 裸分析函数作整个 WHERE 条件(如 WHERE changed_col(true, temp)):值型分析函数返回的是 - // 列值本身(可能是 0/""/false 这类合法变化值),expr AsBool 会失败 → 整条件恒 false 全过滤。 - // 改写为 "占位 != nil":变化/有值=非 nil→true,未变化=nil→false(不受新值是否 0/空串影响)。 - // had_changed 返回 bool,AsBool 可直接处理,不改写(否则 had_changed=false 会被误判 true)。 + // The bare analysis function performs the entire WHERE condition (e.g., WHERE changed_col(true, temp)): the value-type analysis function returns + // The column value itself (possibly valid variants like 0/""/false), expr AsBool will fail → full filtering of integer conditions always false. + // Rewrite as "Placeholder!= nil": Variant/Value = Non-nil→true, Unchanged = nil→false (not affected by whether the new value is 0/empty string). + // had_changed Returns bool, which AsBool can handle directly without rewriting (otherwise, had_changed=false will be false false and false). if len(calls) == 1 && calls[0].FuncName != "had_changed" && strings.TrimSpace(result) == calls[0].Placeholder { result = calls[0].Placeholder + " != nil" } return result, calls, nil } -// parseOverFromString 从 pos 起尝试解析 "over (...)",返回 OverSpec 和消耗后的位置。 -// 无 OVER 时返回 (nil, pos, nil)。 +// parseOverFromString attempts to parse "over (...)" starting from pos, returning the position after OverSpec and consumption. +// Returns when there is no OVER (nil, pos, nil). func parseOverFromString(s string, pos int) (*types.OverSpec, int, error) { j := skipSpaces(s, pos) if j+4 > len(s) || strings.ToLower(s[j:j+4]) != "over" { return nil, pos, nil } after := j + 4 - // over 后必须是非标识符字符(避免 "overfield" 误匹配) + // After over, the character must be a non-identifier (to avoid mismatches with "overfield"). if after < len(s) && (isLetter(s[after]) || isDigit(s[after])) { return nil, pos, nil } @@ -132,8 +133,8 @@ var ( overWhenRe = regexp.MustCompile(`(?i)\bwhen\b\s+(.*)$`) ) -// parseOverBodyString 解析 OVER 体(如 "partition by deviceId, region when status > 0")。 -// 仅支持 PARTITION BY 和 WHEN。 +// parseOverBodyString parses the OVER body (e.g., "partition by deviceId, region when status > 0"). +// Only PARTITION BY and WHEN are supported. func parseOverBodyString(body string) (*types.OverSpec, error) { spec := &types.OverSpec{} if m := overPartitionRe.FindStringSubmatch(body); m != nil { diff --git a/rsql/ast.go b/rsql/ast.go index c9a96e2..2d2e3ec 100644 --- a/rsql/ast.go +++ b/rsql/ast.go @@ -29,7 +29,7 @@ type SelectStatement struct { Having string OrderBy []types.OrderByField JoinConfigs []types.JoinConfig - // MatchRecognize 携带 MATCH_RECOGNIZE 子句(FROM 后、WHERE 前)。非空时走 CEP 路径。 + // MatchRecognize carries MATCH_RECOGNIZE clauses (after FROM, before WHERE clauses). When not idle, follow the CEP path. MatchRecognize *types.MatchRecognizeSpec } @@ -37,7 +37,7 @@ type Field struct { Expression string Alias string AggType string - OverSpec *types.OverSpec // 分析函数 OVER 子句,nil 表示无 + OverSpec *types.OverSpec // Analysis function OVER clause, nil means none } type WindowDefinition struct { @@ -45,12 +45,12 @@ type WindowDefinition struct { Params []any TsProp string TimeUnit time.Duration - MaxOutOfOrderness time.Duration // Maximum allowed out-of-orderness for event time - AllowedLateness time.Duration // Maximum allowed lateness for event time windows - IdleTimeout time.Duration // Idle source timeout: when no data arrives within this duration, watermark advances based on processing time - CountStateTTL time.Duration // Counting-window keyed state TTL; inactive keys reaped after this (0 = disabled) - TriggerCondition string // Global-window TRIGGER WHEN predicate (raw string) - Over *types.OverSpec // GROUP BY window OVER(...) 子句(仅 WHEN 输入门控) + MaxOutOfOrderness time.Duration // Maximum allowed out-of-orderness for event time + AllowedLateness time.Duration // Maximum allowed lateness for event time windows + IdleTimeout time.Duration // Idle source timeout: when no data arrives within this duration, watermark advances based on processing time + CountStateTTL time.Duration // Counting-window keyed state TTL; inactive keys reaped after this (0 = disabled) + TriggerCondition string // Global-window TRIGGER WHEN predicate (raw string) + Over *types.OverSpec // GROUP BY window OVER(...) clause (WHEN input gating only) } // ToStreamConfig converts AST to Stream configuration @@ -95,24 +95,24 @@ func (s *SelectStatement) ToStreamConfig() (*types.Config, string, error) { needWindow := s.Window.Type != "" var simpleFields []string - // 分离分析函数字段:分析函数走直连路径状态机,不进聚合路径。 - // 剩余字段(真聚合 + 普通字段)保持原解析逻辑。 + // Separate analysis function segments: The analysis function follows a direct path to the state machine, without entering the aggregation path. + // The remaining fields (true aggregation + regular fields) retain the original parsing logic. analyticFields := make([]types.AnalyticField, 0, len(s.Fields)) otherFields := make([]Field, 0, len(s.Fields)) for _, f := range s.Fields { if isAnalyticField(f) { - // 校验分析函数自身的嵌套:分析套分析、聚合套分析均不允许 - // (分析套聚合在窗口查询里允许,由 extractInlineAggregates 处理)。 + // Nesting of the verification analysis function itself: Analytical suite analysis and aggregate suite analysis are not allowed + // (Analysis set aggregation is allowed in window queries and handled by extractInlineAggregates). if err := detectNestedAggregation(f.Expression); err != nil { return nil, "", err } analyticFields = append(analyticFields, buildAnalyticField(f)) continue } - // 表达式包分析函数:算术(ts-lag(ts))、标量套(coalesce(lag(temp))、UPPER(lag))、 - // CASE(lag) 等。顶层非裸分析调用,isAnalyticField 为假;含分析调用即路由进分析路径, - // splitAnalyticExprMulti 抽出分析调用、外层表达式作 WrapperExpr 回代。聚合套分析 - // (如 count(lag))由 detectNestedAggregation 拦截。 + // Expression package analysis functions: arithmetic (ts-lag(ts)), scalar set (coalesce(lag(temp)), UPPER(lag)), + // CASE(lag), etc. For top-level non-naked analysis calls, isAnalyticField is false; Includes analysis calls, i.e., routing into analysis paths, + // splitAnalyticExprMulti extracts analysis calls and uses outer expressions as WrapperExpr substitutions. Polymerization set analysis + // (e.g., count(lag)) is intercepted by detectNestedAggregation. if containsAnalyticCall(f.Expression) { if err := detectNestedAggregation(f.Expression); err != nil { return nil, "", err @@ -139,9 +139,9 @@ func (s *SelectStatement) ToStreamConfig() (*types.Config, string, error) { params = []any{10 * time.Second} // Default 10-second window } - // 窗口查询里允许分析函数:分析函数在窗口产出行上求值,状态跨窗口保留 - // (见 stream.processAggregationResults)。分析函数参数里的内联聚合 - // (如 changed_cols("t", true, avg(temperature)))在下方提取为隐藏计算字段。 + // Window queries allow analysis functions: the analysis function evaluates the output row in the window, with the state retained across windows + // (See stream.processAggregationResults.) Analyze inline aggregation within function parameters + // (For example, changed_cols("t", true, avg(temperature))) are extracted below as hidden calculation fields. // If no aggregation functions, collect simple fields if !hasAggregation { @@ -179,12 +179,12 @@ func (s *SelectStatement) ToStreamConfig() (*types.Config, string, error) { return nil, "", err } - // 窗口查询里的分析函数:把参数中的内联聚合(如 changed_cols 内的 avg(...))) - // 提取为隐藏计算字段,重写参数为隐藏键引用,供窗口聚合计算后供分析函数消费。 + // Analysis function in window query: aggregates inline parameters (such as avg(...) in changed_cols)) + // Extract as hidden calculation fields, override parameters as hidden key references, and provide window aggregation for consumption after computation. if needWindow && len(analyticFields) > 0 { extractInlineAggregates(analyticFields, aggs, fields) - // 分析函数默认按 GROUP BY 键分区:跨窗口为每个分组各自保留状态, - // 避免不同分组的窗口输出共享状态而串扰。 + // The analysis function is partitioned by the GROUP BY key by default: each window is reserved for each group's own state, + // Prevents crosstalk caused by sharing the output of windows in different groups. gk := extractGroupFields(s) if len(gk) > 0 { for i := range analyticFields { @@ -197,8 +197,8 @@ func (s *SelectStatement) ToStreamConfig() (*types.Config, string, error) { } } } - // 校验:窗口查询里分析函数的参数必须引用窗口输出字段(聚合或 GROUP BY 键), - // 不能引用裸原始列——否则求值时取不到值,会静默得到列名字符串而非结果。 + // Check: In window queries, the parameters of the analysis function must reference the window output field (aggregation or GROUP BY key). + // You cannot reference the raw columns—otherwise, if you don't get a value during evaluation, you'll silently get the column string instead of the result. if err := validateWindowAnalyticArgs(analyticFields, gk); err != nil { return nil, "", err } @@ -234,19 +234,19 @@ func (s *SelectStatement) ToStreamConfig() (*types.Config, string, error) { timeCharacteristic = types.EventTime } - // GROUP BY 窗口不支持 OVER(...):窗口 OVER 的输入门控语义会隐藏 dip、破坏检测, - // 阈值/持续检测用 HAVING(如 HAVING min(concurrency) > 200)。 + // The GROUP BY window does not support OVER(...): The input gate semantics of window OVER hide dip and corruption detection, + // Threshold/continuous detection is used for HAVING (e.g., HAVING min(concurrency) > 200). if s.Window.Over != nil { return nil, "", fmt.Errorf("OVER(...) on a GROUP BY window is not supported; for threshold/sustained detection use HAVING (e.g. HAVING min(concurrency) > 200)") } - // HAVING 可引用未选出的聚合(标准 SQL)。把 HAVING 文本里的聚合调用 - // 映射到已选 alias,或注册为隐藏聚合 __having_N__ 让 aggregator 补算;aggs/fields 原地扩充。 + // HAVING can reference unselected aggregates (standard SQL). Call the aggregation in the HAVING text + // Map to selected alias, or register as a hidden aggregation __having_N__ to have aggregator complete the calculation; aggs/fields expanded in place. selectAlias := buildSelectAliasMap(s.Fields) havingRewritten := extractHavingAggregates(s.Having, aggs, fields, selectAlias) - // 执行路径模式:MATCH_RECOGNIZE→CEP;窗口/聚合→Window;否则 Direct。 - // 拦截 MATCH_RECOGNIZE 与 GROUP/聚合、JOIN 的组合(后续阶段支持)。 + // Execution path mode: MATCH_RECOGNIZE→ CEP; Window/Aggregate→Window; Otherwise, use Direct. + // Interception MATCH_RECOGNIZE combined with GROUP/aggregation and JOIN (supported in later stages). mode := types.ExecDirect if needWindow { mode = types.ExecWindow @@ -264,7 +264,7 @@ func (s *SelectStatement) ToStreamConfig() (*types.Config, string, error) { if len(s.MatchRecognize.OrderBy) == 0 { return nil, "", fmt.Errorf("MATCH_RECOGNIZE requires ORDER BY (provides event ordering)") } - // ORDER BY 在 CEP 提供事件时序字段;DESC 流式下无意义(按到达序),拒绝以免静默忽略。 + // ORDER BY provides event timing fields in CEP; DESC is meaningless in streaming (in order of arrival), refusal to avoid silent ignoring. for _, ob := range s.MatchRecognize.OrderBy { if ob.Direction == types.SortDesc { return nil, "", fmt.Errorf("MATCH_RECOGNIZE ORDER BY 暂不支持 DESC(流式按到达序处理)") @@ -311,7 +311,7 @@ func (s *SelectStatement) ToStreamConfig() (*types.Config, string, error) { SourceAlias: s.SourceAlias, } - // 提取 WHERE 中的分析函数调用(含 OVER),替换为占位符,供直连路径状态机求值。 + // Extract the analysis function call (including OVER) from WHERE and replace it with placeholders for direct path state machine evaluation. rewrittenCondition, whereCalls, err := extractWhereAnalyticCalls(s.Condition) if err != nil { return nil, "", err @@ -321,7 +321,7 @@ func (s *SelectStatement) ToStreamConfig() (*types.Config, string, error) { return &config, rewrittenCondition, nil } -// isAnalyticField 判断 Field 是否为分析函数(TypeAnalytical)。 +// isAnalyticField checks whether a field is an analysis function (TypeAnalytical). func isAnalyticField(f Field) bool { funcName := extractFunctionName(f.Expression) if funcName == "" { @@ -333,10 +333,10 @@ func isAnalyticField(f Field) bool { return false } -// containsAnalyticCall 判断表达式里是否含分析函数(TypeAnalytical)调用。 -// 用于拦截把分析函数嵌进标量函数的写法(如 UPPER(changed_col(...))): -// 分析函数需跨行状态,走无状态标量路径会静默求错,必须在解析期拒绝。 -// 先去掉字符串字面量,避免把字面量里形如 "lag(" 的文本误判为函数调用。 +// containsAnalyticCall checks whether the expression contains a TypeAnalytical call. +// Intercept and embed analysis functions into scalar functions (such as UPPER(changed_col(...))): +// The analysis function must cross lines; using a stateless scalar path will silently seek errors and must be rejected during the parsing period. +// First, remove string literals to avoid misinterpreting text like "lag(") as function calls. func containsAnalyticCall(expr string) bool { for _, name := range extractAllFunctions(stripStringLiterals(expr)) { if fn, exists := functions.Get(name); exists && fn.GetType() == functions.TypeAnalytical { @@ -346,27 +346,27 @@ func containsAnalyticCall(expr string) bool { return false } -// stripStringLiterals 去掉字符串字面量内容,仅保留字面量外的表达式文本。 -// 本方言单引号 '...' 与双引号 "..." 都是字符串字面量(如 changed_cols("t",...)), -// 二者都要剥离,否则 "lag(x)" 这类双引号字面量里的分析函数名会被误判为调用。 -// 处理 SQL 转义的两个连续引号('' 或 "")。 +// stripStringLiterals removes the string literal content, keeping only the expression text outside the literal. +// This dialect single quotation mark '...' and double quotation marks "..." are all string literals (e.g., changed_cols("t",...)), +// Both must be stripped away; otherwise, analysis function names in double-quoted literals like "lag(x)" will be mistakenly interpreted as calls. +// Handle two consecutive quotes (” or "") in SQL escaping. func stripStringLiterals(expr string) string { var b strings.Builder b.Grow(len(expr)) - var quote byte // 0=不在字面量内;'\''|'"'=当前字面量定界符 + var quote byte // 0 = not included in literal values; '\''|'"' = Current literal delimiter for i := 0; i < len(expr); i++ { c := expr[i] if c == '\'' || c == '"' { if quote == c && i+1 < len(expr) && expr[i+1] == c { - i++ // 转义引号,跳过,仍处于字面量内 + i++ // Escape quotation marks and skip it, still within the literal range continue } if quote == 0 { - quote = c // 进入字面量 + quote = c // Let's go to the literal amount } else if quote == c { - quote = 0 // 字面量结束 + quote = 0 // Literally, the amount ends } - // 异类引号(如 " 内的 ')当作字面量内容跳过,不改状态 + // Treat mismatched quotes (for example, ' inside ") as literal content without changing state continue } if quote == 0 { @@ -376,10 +376,10 @@ func stripStringLiterals(expr string) string { return b.String() } -// buildAnalyticField 将分析函数 Field 转为 AnalyticField,保留 OVER 子句。 -// 支持"表达式包分析函数"(如 ts - lag(ts)):拆出裸分析调用供状态机计算,外层表达式 -// 存为 WrapperExpr(分析调用替换为 types.AnalyticSelfToken)供求值期回代。 -// 同一表达式含多个分析调用(如 acc_max(v) - acc_min(v))时抽出全部,各分配独立占位。 +// buildAnalyticField converts the analysis function Field to AnalyticField, retaining the OVER clause. +// Supports "expression package analysis functions" (e.g., ts - lag(ts)): split the bare analysis call for state machine calculations, outer expressions +// Store as WrapperExpr (parse call replaced with types.AnalyticSelfToken) supply and demand value periods for a period of regeneration. +// When the same expression contains multiple analysis calls (e.g., acc_max(v) - acc_min(v)), all are extracted, and each allocation occupies its own place. func buildAnalyticField(f Field) types.AnalyticField { calls, wrapper := splitAnalyticExprMulti(f.Expression) alias := f.Alias @@ -396,7 +396,7 @@ func buildAnalyticField(f Field) types.AnalyticField { af.FuncName = strings.ToLower(calls[0].FuncName) af.Expression = calls[0].BareCall af.Args = calls[0].Args - // changed_cols 输出多列(动态列名 prefix+colname),仅 SELECT。 + // changed_cols Outputs multiple columns (dynamic column name prefix+colname), only SELECT. if af.FuncName == "changed_cols" { af.MultiColumn = true } @@ -404,9 +404,9 @@ func buildAnalyticField(f Field) types.AnalyticField { return af } -// splitAnalyticExprMulti 抽出表达式里的全部 Analytic 调用(按出现顺序),各调用子串替换为 -// types.AnalyticSelfTokenN(i) 占位构成 wrapper。纯单调用且覆盖整式时 wrapper=""(纯分析字段语义); -// 否则 wrapper 含占位。不含分析调用时返回 (nil, "")。 +// splitAnalyticExprMulti extracts all Analytic calls from the expression (in order they appear), replacing each call substring with +// types.AnalyticSelfTokenN(i) spaced to form the wrapper. When calling a single and overwriting the polynomial, wrapper=""(pure analysis field semantics); +// Otherwise, wrapper includes placeholders. Returns when not using parse calls (nil, ""). func splitAnalyticExprMulti(expr string) (calls []types.AnalyticCall, wrapper string) { pattern := regexp.MustCompile(`(?i)\b([a-z_]+)\s*\(`) type span struct{ start, closeParen int } @@ -422,7 +422,7 @@ func splitAnalyticExprMulti(expr string) (calls []types.AnalyticCall, wrapper st if cp < 0 { continue } - // 跳过嵌套在已记录调用内的匹配(分析套分析不允许,稳妥起见不计入)。 + // Skips nested matches within recorded calls (analysis set analysis is not allowed, so it is not counted for safety). nested := false for _, s := range spans { if m[0] > s.start && cp < s.closeParen { @@ -443,7 +443,7 @@ func splitAnalyticExprMulti(expr string) (calls []types.AnalyticCall, wrapper st if len(calls) == 0 { return nil, "" } - // 纯单调用且覆盖整式:wrapper 留空(保持纯分析字段语义)。 + // Pure call and override polynomial: wrapper is left blank (keeps the semantics of the pure analysis field). if len(calls) == 1 { leading := len(expr) - len(strings.TrimLeft(expr, " \t")) trailing := len(strings.TrimRight(expr, " \t")) @@ -452,7 +452,7 @@ func splitAnalyticExprMulti(expr string) (calls []types.AnalyticCall, wrapper st return calls, "" } } - // 从右向左替换每个调用 span 为占位,避免索引位移。 + // Replace each call span from right to left to avoid index shift. wrapper = expr for i := len(spans) - 1; i >= 0; i-- { s := spans[i] @@ -461,19 +461,19 @@ func splitAnalyticExprMulti(expr string) (calls []types.AnalyticCall, wrapper st return calls, wrapper } -// extractInlineAggregates 把分析函数参数里的内联聚合提取为隐藏计算字段。 -// 如 changed_cols("t", true, avg(temperature)) → 注册 aggs[__winagg_0__]=avg(temperature), -// 参数重写为 __winagg_0__,InlineAggDisplay 记录 {__winagg_0__:"avg"}。 -// 复合表达式参数(如 avg(temp) + 1)只替换其中的聚合调用 span,外层运算符保留, -// 参数变为 __winagg_0__ + 1,运行期由表达式求值器计算。 -// 隐藏键前缀 __winagg_ 在窗口产出后被剥离,不进最终输出;显示名用于 changed_cols 输出列名。 +// extractInlineAggregates extracts the inline aggregates from the analysis function parameters into hidden computed fields. +// For example, changed_cols("t", true, avg(temperature)) → register aggs[__winagg_0__]=avg(temperature), +// The parameter is rewritten to __winagg_0__, and InlineAggDisplay records {__winagg_0__:"avg"}. +// Compound expression parameters (such as avg(temp) + 1) only replace the aggregate call span, while the outer operator is retained, +// The parameter becomes __winagg_0__ + 1, and the runtime is calculated by the expression evaluator. +// Hidden key prefix __winagg_ is stripped after window output and not entered for final output; The display name is used to changed_cols output column names. func extractInlineAggregates(analyticFields []types.AnalyticField, aggs map[string]aggregator.AggregateType, fieldMap map[string]string) { pattern := regexp.MustCompile(`(?i)\b([a-z_]+)\s*\(`) seq := 0 for i := range analyticFields { af := &analyticFields[i] - // 遍历字段内每个分析调用的参数,抽内联聚合并就地重写该调用(Args+BareCall)。 - // 无 Calls(不应发生在 SELECT 字段,稳妥兜底)退化为单调用。 + // Traverse each parameter of the analysis call within the field, extract inline aggregation, and override the call in place (Args+BareCall). + // No Calls (should not occur in the SELECT field, as a safe backup) has degenerated into single calls. if len(af.Calls) == 0 { af.Calls = []types.AnalyticCall{{FuncName: af.FuncName, BareCall: af.Expression, Args: af.Args}} } @@ -490,14 +490,14 @@ func extractInlineAggregates(analyticFields []types.AnalyticField, aggs map[stri if !ok || fn.GetType() != functions.TypeAggregation { continue } - // 聚合调用 span:从函数名起点到匹配的右括号(idx[1]-1 是 '(' 的位置)。 + // Aggregation call span: from the function name start to the matching right parenthesis (idx[1]-1 is the position of '('). openParen := idx[1] - 1 closeParen := findMatchingParenInternal(trimmed, openParen) if closeParen < 0 { continue } aggCall := trimmed[idx[0] : closeParen+1] - // 只解析聚合调用本身(不含外层运算符),避免整参被误判为 "expression" 型聚合。 + // Only parse the aggregation call itself (excluding the outer operator) to avoid the full parameter being mistakenly identified as an "expression" aggregation. aggType, name, _, _, perr := ParseAggregateTypeWithExpression(aggCall) if perr != nil || aggType == "" { continue @@ -505,8 +505,8 @@ func extractInlineAggregates(analyticFields []types.AnalyticField, aggs map[stri hidden := fmt.Sprintf("__winagg_%d__", seq) seq++ aggs[hidden] = aggType - // 输入字段:name 为聚合的输入字段(如 avg(temperature) 的 temperature); - // 无显式字段时(如 count(*))用隐藏键本身,聚合器按需处理。 + // Input field: name is the input field for aggregation (e.g., temperature in avg(temperature)); + // When there is no explicit field (e.g., count(*)), use the hidden key itself, and the aggregator handles it as needed. if name != "" { fieldMap[hidden] = name } else { @@ -516,19 +516,19 @@ func extractInlineAggregates(analyticFields []types.AnalyticField, aggs map[stri af.InlineAggDisplay = make(map[string]string) } af.InlineAggDisplay[hidden] = funcName - // 仅替换聚合调用子串,保留外层运算符(复合表达式)。 + // Only replace the aggregation call substring, retaining the outer operator (composite expression). args[j] = trimmed[:idx[0]] + hidden + trimmed[closeParen+1:] af.Calls[ci].BareCall = strings.Replace(af.Calls[ci].BareCall, aggCall, hidden, 1) } } - // 旧路径(changed_cols 多列扇出等)仍读 Expression/Args,与首个调用保持同步。 + // Old paths (changed_cols multi-column fanouts, etc.) still read Expression/Args and remain synchronized with the first call. af.Expression = af.Calls[0].BareCall af.Args = af.Calls[0].Args } } -// collapseSpacesOutsideQuotes 去掉引号外的空白,引号内(字符串字面量)保留原样。 -// 用于归一化 HAVING 里带空格的聚合调用文本(parser 存为 "max ( v )"),便于复用解析函数。 +// collapseSpacesOutsideQuotes Remove the blank space outside the quotes, while keeping the string literal in quotes as is. +// Normalizes aggregate calls containing spaces in HAVING (stored by the parser as "max ( v )") so the parsing function can be reused. func collapseSpacesOutsideQuotes(s string) string { var b strings.Builder b.Grow(len(s)) @@ -552,12 +552,13 @@ func collapseSpacesOutsideQuotes(s string) string { return b.String() } -// extractHavingAggregates 处理 HAVING 引用的聚合(标准 SQL:HAVING 可引用任意聚合,不必在 SELECT)。 -// 对 HAVING 文本里每个聚合调用 ac: -// - selectAlias[ac] 命中(SELECT 里 ac AS alias)→ 改写 HAVING 里 ac 为 alias(聚合已在算)。 -// - aggs[ac] 命中(无别名选出,键恰为调用文本)→ 不动。 -// - 否则(未选出)→ 注册隐藏聚合 __having_N__(aggs/fieldMap 原地扩充),ac 改写为 __having_N__。 -// 返回改写后的 HAVING 文本。aggs/fieldMap 为 map 引用,原地修改。 +// extractHavingAggregates handles aggregations referenced by HAVING (standard SQL: HAVING can reference arbitrary aggregations without having to be in SELECT). +// For each aggregation in the HAVING text, call ac: +// - selectAlias[ac] hits (ac AS alias in SELECT) → rewrites ac in HAVING to alias (aggregation already calculated). +// - aggs[ac] hits (no alias, selected, key is called text) → does not move. +// - Otherwise (not selected) → register hidden aggregation __having_N__ (aggs/fieldMap in-place expansion), rewrite ac as __having_N__. +// +// Returns the rewritten HAVING text. aggs/fieldMap is a map reference, modified in place. func extractHavingAggregates(having string, aggs map[string]aggregator.AggregateType, fieldMap map[string]string, selectAlias map[string]string) string { if strings.TrimSpace(having) == "" { return having @@ -596,7 +597,7 @@ func extractHavingAggregates(having string, aggs map[string]aggregator.Aggregate } aggType, name, _, _, perr := ParseAggregateTypeWithExpression(collapseSpacesOutsideQuotes(ac)) if perr != nil || aggType == "" { - repl[i] = ac // 解析失败原样保留(求值落空但不破坏文本) + repl[i] = ac // Retain parsing failure as is (evaluation fails but does not corrupt text) continue } hidden := fmt.Sprintf("__having_%d__", seq) @@ -617,10 +618,10 @@ func extractHavingAggregates(having string, aggs map[string]aggregator.Aggregate return out } -// validateWindowAnalyticArgs 校验窗口查询里分析函数参数不得引用裸原始列: -// 窗口产出行只含聚合与 GROUP BY 键,裸列取不到值会静默得到列名字符串。 -// 允许:字面量、__winagg_ 隐藏聚合键、GROUP BY 键、函数调用、复杂表达式(含运算符)。 -// 仅拦截"裸列名且非 GROUP BY 键"这一最常见误用。 +// validateWindowAnalyticArgs The parsing function parameters in validateWindowAnalyticArgs queries must not reference the raw raw columns: +// The window produces rows that only contain aggregation and GROUP BY keys; if a bare column does not get a value, it will silently receive a column name string. +// Allowed: literals, __winagg_ Hide aggregation keys, GROUP BY keys, function calls, complex expressions (including operators). +// Only intercepts the most common misuse of "bare column name and not GROUP BY key." func validateWindowAnalyticArgs(analyticFields []types.AnalyticField, groupKeys []string) error { keySet := make(map[string]bool, len(groupKeys)) for _, k := range groupKeys { @@ -635,7 +636,7 @@ func validateWindowAnalyticArgs(analyticFields []types.AnalyticField, groupKeys if isLiteralToken(a) { continue } - // 函数调用或含运算符的复杂表达式:聚合提取已处理 func(),此处不深判。 + // Function calls or complex expressions containing operators: aggregate extraction of processed func(), not detailed here. if strings.ContainsAny(a, " ()<>=+-*/%") { continue } @@ -651,7 +652,7 @@ func validateWindowAnalyticArgs(analyticFields []types.AnalyticField, groupKeys return nil } -// isLiteralToken 判断是否为字面量(数字/布尔/nil/引号字符串)。 +// isLiteralToken checks whether it is a literal quantity (number/boolean/nil/quotation string). func isLiteralToken(s string) bool { if s == "true" || s == "false" || s == "nil" { return true @@ -666,9 +667,9 @@ func isLiteralToken(s string) bool { return (c >= '0' && c <= '9') || c == '-' || c == '+' || c == '.' } -// splitCallArgs 从函数调用文本中拆出顶层参数表达式片段(未求值)。 -// 如 changed_cols("c_", true, temperature, humidity) → ["\"c_\"", "true", "temperature", "humidity"]。 -// 解析失败或无参时返回 nil。 +// splitCallArgs extracts a segment of top-level parameter expression from the function call text (not evaluated). +// For example, changed_cols("c_", true, temperature, humidity) → ["\"c_\"", "true", "temperature", "humidity"]. +// Returns nil when parsing fails or has no parameters. func splitCallArgs(expr string) []string { open := strings.IndexByte(expr, '(') if open < 0 { @@ -682,7 +683,7 @@ func splitCallArgs(expr string) []string { return splitTopLevelCommas(body) } -// splitTopLevelCommas 按顶层逗号拆分,忽略嵌套括号与字符串字面量内的逗号。 +// splitTopLevelCommas splits commas by top-level commas, ignoring commas within nested parentheses and string literals. func splitTopLevelCommas(s string) []string { var args []string depth := 0 @@ -719,8 +720,8 @@ func splitTopLevelCommas(s string) []string { } // groupKeyIsScalarFunctionExpr reports whether expr is a function expression whose -// top-level function is a registered scalar (非聚合/分析/窗口) function。用于放行 -// GROUP BY upper(device) 这类函数表达式分组键,同时拒绝拼错窗口函数的泄漏(如 Foo(5))。 +// The top-level function is a registered scalar (non-aggregation/analysis/window) function. Used for release +// GROUP BY upper(device) Function expressions like this group key, while rejecting misspelled window functions (e.g., Foo(5)). func groupKeyIsScalarFunctionExpr(expr string) bool { fn, exists := functions.Get(strings.ToLower(extractFunctionName(expr))) if !exists { @@ -805,7 +806,7 @@ func extractFieldOrder(fields []Field) ([]string, error) { func extractGroupFields(s *SelectStatement) []string { var fields []string for _, f := range s.GroupBy { - // 保留裸列与标量函数表达式(如 upper(device));只排除聚合函数当分组键(无意义)。 + // Retain bare lists and scalar function expressions (such as upper(device)); Only excludes aggregation functions as grouping keys (meaningless). if isAggregationFunction(f) { continue } @@ -865,16 +866,17 @@ func buildSelectFields(fields []Field) (aggMap map[string]aggregator.AggregateTy return selectFields, fieldMap, nil } -// detectNestedAggregation 检测表达式中是否存在聚合函数嵌套聚合函数的情况 -// 如果发现嵌套聚合函数,返回错误信息 +// detectNestedAggregation detects whether the expression contains nested aggregation functions +// If nested aggregation functions are found, an error message is returned func detectNestedAggregation(expr string) error { return detectNestedAggregationRecursive(expr, false, false) } -// detectNestedAggregationRecursive 递归检测嵌套聚合/分析函数。 -// inAggregation:当前在真聚合(TypeAggregation)内部;inAnalytic:当前在分析函数内部。 -// 规则:聚合套聚合 → 报错;分析套分析 → 报错;聚合套分析 → 报错; -// 分析套聚合 → 允许(如 changed_cols(avg(...)),分析函数对窗口聚合输出求值)。 +// detectNestedAggregationRecursive: Recursive detection of nested aggregation/analysis functions. +// inAggregation: currently inside the true aggregation (TypeAggregation); inAnalytic: Currently inside the analysis function. +// Rule: Aggregation set aggregation → Error report; Analysis set analysis → error reporting; Aggregation set analysis → error reporting; +// +// Analyze the aggregation → allowed (e.g., changed_cols(avg(...)), the analysis function evaluates the output of the window aggregation). func detectNestedAggregationRecursive(expr string, inAggregation, inAnalytic bool) error { pattern := regexp.MustCompile(`(?i)([a-z_]+)\s*\(`) matches := pattern.FindAllStringSubmatchIndex(expr, -1) @@ -888,12 +890,12 @@ func detectNestedAggregationRecursive(expr string, inAggregation, inAnalytic boo if ft == functions.TypeAggregation || ft == functions.TypeAnalytical || ft == functions.TypeWindow { switch ft { case functions.TypeAggregation: - // 聚合函数内部不能再套聚合函数。 + // Aggregation functions cannot be embedded inside the aggregation function. if inAggregation { return fmt.Errorf("aggregate function calls cannot be nested") } case functions.TypeAnalytical: - // 分析套分析、或聚合套分析 → 报错(分析函数只可包裹聚合)。 + // Analyzer set analysis, or aggregation set analysis → error (the parser function can only wrap aggregation). if inAnalytic || inAggregation { return fmt.Errorf("analytic functions cannot be nested in %s", funcName) } @@ -906,8 +908,8 @@ func detectNestedAggregationRecursive(expr string, inAggregation, inAnalytic boo if funcEnd > funcStart { paramStart := funcStart + len(funcName) + 1 params := expr[paramStart:funcEnd] - // 进入真聚合:标记 inAggregation;进入分析:标记 inAnalytic(不标记 inAggregation, - // 这样分析函数内部允许再出现聚合,即"分析套聚合")。 + // Enter true aggregation: mark inAggregation; Enter the analysis: Mark inAnalytic (do not mark inAggregation, + // This allows further aggregation within the analysis function, i.e., "analysis set aggregation"). nextAgg := inAggregation || ft == functions.TypeAggregation nextAna := inAnalytic || ft == functions.TypeAnalytical if err := detectNestedAggregationRecursive(params, nextAgg, nextAna); err != nil { @@ -923,9 +925,9 @@ func detectNestedAggregationRecursive(expr string, inAggregation, inAnalytic boo // Parse aggregation function and return expression information func ParseAggregateTypeWithExpression(exprStr string) (aggType aggregator.AggregateType, name string, expression string, allFields []string, err error) { - // 首先检测是否存在嵌套聚合函数 + // First, check for nested aggregation functions if err := detectNestedAggregation(exprStr); err != nil { - // 如果发现嵌套聚合,返回错误 + // If nested aggregation is found, an error is returned return "", "", "", nil, err } @@ -1468,7 +1470,7 @@ func buildSelectFieldsWithExpressions(fields []Field) ( // Handle as regular expression t, n, expression, allFields, parseErr := ParseAggregateTypeWithExpression(f.Expression) if parseErr != nil { - // 如果检测到嵌套聚合函数,返回错误 + // If nested aggregation functions are detected, an error is returned return nil, nil, nil, nil, parseErr } if t != "" { @@ -1615,34 +1617,34 @@ func parseComplexAggregationExpression(expr string) ([]types.AggregationFieldInf // parseComplexAggExpressionInternal implements the actual parsing logic func parseComplexAggExpressionInternal(expr string) ([]types.AggregationFieldInfo, string, error) { - // 首先检测嵌套聚合 + // First, detect nested polymerization if err := detectNestedAggregation(expr); err != nil { return nil, "", err } - // 使用改进的递归解析方法 + // Using an improved recursive analysis method aggFields, exprTemplate := parseNestedFunctionsInternal(expr, make([]types.AggregationFieldInfo, 0)) return aggFields, exprTemplate, nil } -// parseNestedFunctionsInternal 递归解析嵌套函数调用 +// parseNestedFunctionsInternal Recursive Parsing nested function call func parseNestedFunctionsInternal(expr string, aggFields []types.AggregationFieldInfo) ([]types.AggregationFieldInfo, string) { - // 匹配函数调用,支持大小写不敏感 + // Match function calls, supporting case-insensitive calls pattern := regexp.MustCompile(`(?i)([a-z_]+)\s*\(`) - // 找到所有函数调用的起始位置 + // Find the starting position of all function calls matches := pattern.FindAllStringSubmatchIndex(expr, -1) if len(matches) == 0 { return aggFields, expr } - // 从右到左处理,避免索引偏移问题 + // Handle from right to left to avoid index offset issues for i := len(matches) - 1; i >= 0; i-- { match := matches[i] funcStart := match[0] funcName := strings.ToLower(expr[match[2]:match[3]]) - // 找到匹配的右括号 + // Find the matching right parenthesis parenStart := match[3] parenEnd := findMatchingParenInternal(expr, parenStart) if parenEnd == -1 { @@ -1652,11 +1654,11 @@ func parseNestedFunctionsInternal(expr string, aggFields []types.AggregationFiel fullFuncCall := expr[funcStart : parenEnd+1] funcParam := expr[parenStart+1 : parenEnd] - // 检查是否是聚合函数 + // Check if it is an aggregate function if fn, exists := functions.Get(funcName); exists { switch fn.GetType() { case functions.TypeAggregation, functions.TypeAnalytical, functions.TypeWindow: - // 生成唯一占位符 + // Generates a unique placeholder callHash := 0 for _, c := range fullFuncCall { callHash = callHash*31 + int(c) @@ -1666,10 +1668,10 @@ func parseNestedFunctionsInternal(expr string, aggFields []types.AggregationFiel } placeholder := fmt.Sprintf("__%s_%d__", funcName, callHash) - // 解析函数参数 + // Parsing function parameters inputField := strings.TrimSpace(funcParam) - // 对于聚合函数,如果参数包含嵌套函数调用,保留完整参数 - // 只有在参数是简单的逗号分隔列表时才进行分割 + // For aggregate functions, if the parameters contain nested function calls, keep the full argument + // Segmentation is only performed when the parameters are simply comma-separated lists if strings.Contains(funcParam, ",") && !containsNestedFunctions(funcParam) { params := strings.Split(funcParam, ",") if len(params) > 0 { @@ -1677,7 +1679,7 @@ func parseNestedFunctionsInternal(expr string, aggFields []types.AggregationFiel } } - // 添加到聚合字段列表 + // Add to the aggregated field list fieldInfo := types.AggregationFieldInfo{ FuncName: funcName, InputField: inputField, @@ -1687,7 +1689,7 @@ func parseNestedFunctionsInternal(expr string, aggFields []types.AggregationFiel } aggFields = append(aggFields, fieldInfo) - // 替换表达式中的聚合函数调用 + // Replace aggregation function calls in expressions expr = expr[:funcStart] + placeholder + expr[parenEnd+1:] } } @@ -1696,14 +1698,14 @@ func parseNestedFunctionsInternal(expr string, aggFields []types.AggregationFiel return aggFields, expr } -// containsNestedFunctions 检查参数字符串是否包含嵌套函数调用 +// containsNestedFunctions checks whether the parameter string contains nested function calls func containsNestedFunctions(param string) bool { - // 简单检查:如果包含函数名模式后跟括号,则认为是嵌套函数 + // Simple check: If the function name pattern is followed by parentheses, it is considered a nested function pattern := regexp.MustCompile(`[a-zA-Z_][a-zA-Z0-9_]*\s*\(`) return pattern.MatchString(param) } -// findMatchingParenInternal 找到匹配的右括号 +// findMatchingParenInternal Find the right bracket of the match func findMatchingParenInternal(s string, start int) int { if start >= len(s) || s[start] != '(' { return -1 @@ -1721,5 +1723,5 @@ func findMatchingParenInternal(s string, start int) int { } } } - return -1 // 未找到匹配的右括号 + return -1 // No matching right bracket found } diff --git a/rsql/ast_test.go b/rsql/ast_test.go index 8de2def..d9d3d01 100644 --- a/rsql/ast_test.go +++ b/rsql/ast_test.go @@ -7,7 +7,7 @@ import ( "github.com/rulego/streamsql/window" ) -// TestSelectStatement_ToStreamConfig 测试 SelectStatement 转换为 Stream 配置 +// TestSelectStatement_ToStreamConfig Test the conversion of SelectStatement to Stream configuration func TestSelectStatement_ToStreamConfig(t *testing.T) { tests := []struct { name string @@ -209,9 +209,9 @@ func TestSelectStatement_ToStreamConfig(t *testing.T) { } } -// TestSelectStatementEdgeCases 测试边界情况 +// TestSelectStatementEdgeCases tests boundary conditions func TestSelectStatementEdgeCases(t *testing.T) { - // 测试空字段列表 + // Test the list of empty fields stmt := &SelectStatement{ Fields: []Field{}, Source: "test_table", @@ -230,7 +230,7 @@ func TestSelectStatementEdgeCases(t *testing.T) { t.Errorf("Expected empty condition, got %s", condition) } - // 测试复杂窗口类型 + // Test complex window types stmt2 := &SelectStatement{ Fields: []Field{ {Expression: "COUNT(*)", Alias: "count"}, @@ -256,7 +256,7 @@ func TestSelectStatementEdgeCases(t *testing.T) { } } -// TestSelectStatementConcurrency 测试并发安全性 +// TestSelectStatementConcurrency tests concurrency security func TestSelectStatementConcurrency(t *testing.T) { stmt := &SelectStatement{ Fields: []Field{ @@ -270,7 +270,7 @@ func TestSelectStatementConcurrency(t *testing.T) { }, } - // 启动多个 goroutine 并发调用 ToStreamConfig + // Start multiple goroutines and call ToStreamConfig concurrently done := make(chan bool, 10) for i := 0; i < 10; i++ { go func() { @@ -293,13 +293,13 @@ func TestSelectStatementConcurrency(t *testing.T) { }() } - // 等待所有 goroutine 完成 + // Wait for all goroutines to complete for i := 0; i < 10; i++ { <-done } } -// TestBuildSelectFields 测试 buildSelectFields 函数 +// TestBuildSelectFields tests the buildSelectFields function func TestBuildSelectFields(t *testing.T) { tests := []struct { name string @@ -366,7 +366,7 @@ func TestBuildSelectFields(t *testing.T) { return } - // 检查聚合函数映射 + // Check the aggregation function mapping if len(aggMap) != len(tt.wantAggs) { t.Errorf("buildSelectFields() aggMap length = %d, want %d", len(aggMap), len(tt.wantAggs)) } @@ -376,7 +376,7 @@ func TestBuildSelectFields(t *testing.T) { } } - // 检查字段映射 + // Check the field mapping if len(fieldMap) != len(tt.wantMap) { t.Errorf("buildSelectFields() fieldMap length = %d, want %d", len(fieldMap), len(tt.wantMap)) } @@ -389,7 +389,7 @@ func TestBuildSelectFields(t *testing.T) { } } -// TestIsAggregationFunction 测试 isAggregationFunction 函数 +// TestIsAggregationFunction Test the isAggregationFunction function func TestIsAggregationFunction(t *testing.T) { tests := []struct { name string @@ -408,7 +408,7 @@ func TestIsAggregationFunction(t *testing.T) { {"表达式", "temperature + 10", false}, {"UPPER函数", "UPPER(name)", false}, {"CONCAT函数", "CONCAT(first_name, last_name)", false}, - {"未知函数", "UNKNOWN_FUNC(field)", true}, // 保守处理 + {"未知函数", "UNKNOWN_FUNC(field)", true}, // Handle it conservatively {"复杂表达式", "temperature > 25 AND humidity < 80", false}, } @@ -421,7 +421,7 @@ func TestIsAggregationFunction(t *testing.T) { } } -// TestParseAggregateTypeWithExpression 测试 ParseAggregateTypeWithExpression 函数 +// TestParseAggregateTypeWithExpression Test the ParseAggregateTypeWithExpression function func TestParseAggregateTypeWithExpression(t *testing.T) { tests := []struct { name string @@ -502,7 +502,7 @@ func TestParseAggregateTypeWithExpression(t *testing.T) { }, } - // 测试正常情况 + // Test for normal conditions for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { aggType, name, expression, allFields, err := ParseAggregateTypeWithExpression(tt.exprStr) @@ -534,7 +534,7 @@ func TestParseAggregateTypeWithExpression(t *testing.T) { }) } - // 测试嵌套聚合函数检测 + // Test nested aggregation function detection nestedTests := []struct { name string exprStr string @@ -565,7 +565,7 @@ func TestParseAggregateTypeWithExpression(t *testing.T) { } } -// TestDetectNestedAggregation 测试嵌套聚合函数检测 +// TestDetectNestedAggregation Tests nested aggregation function detection func TestDetectNestedAggregation(t *testing.T) { tests := []struct { name string @@ -611,7 +611,7 @@ func TestDetectNestedAggregation(t *testing.T) { } } -// TestExtractAggFieldWithExpression 测试 extractAggFieldWithExpression 函数 +// TestExtractAggFieldWithExpression Test extractAggFieldWithExpression function func TestExtractAggFieldWithExpression(t *testing.T) { tests := []struct { name string @@ -654,7 +654,7 @@ func TestExtractAggFieldWithExpression(t *testing.T) { funcName: "DISTANCE", wantFieldName: "x1", wantExpression: "x1, y1, x2, y2", - // 不检查 allFields,因为实际行为可能与预期不同 + // Not checking allFields, because actual behavior may differ from expectations }, { name: "无效表达式", diff --git a/rsql/coverage_test.go b/rsql/coverage_test.go index a91462d..c11e72c 100644 --- a/rsql/coverage_test.go +++ b/rsql/coverage_test.go @@ -9,7 +9,7 @@ import ( "github.com/rulego/streamsql/window" ) -// TestParseSmartParameters 测试智能参数解析函数 +// TestParseSmartParameters tests the intelligent parameter parsing function func TestParseSmartParameters(t *testing.T) { tests := []struct { name string @@ -64,7 +64,7 @@ func TestParseSmartParameters(t *testing.T) { } } -// TestExpectTokenSuccess 测试expectToken函数成功情况 +// TestExpectTokenSuccess Tests the success status of the expectToken function func TestExpectTokenSuccess(t *testing.T) { lexer := NewLexer("SELECT") parser := &Parser{lexer: lexer, errorRecovery: NewErrorRecovery(&Parser{})} @@ -78,7 +78,7 @@ func TestExpectTokenSuccess(t *testing.T) { } } -// TestExpectTokenFailure 测试expectToken函数失败情况 +// TestExpectTokenFailure tests the expectToken function failure func TestExpectTokenFailure(t *testing.T) { lexer := NewLexer("FROM") parser := &Parser{lexer: lexer} @@ -93,22 +93,22 @@ func TestExpectTokenFailure(t *testing.T) { } } -// TestExpectTokenWithRecovery 测试expectToken函数错误恢复情况 +// TestExpectTokenWithRecovery tests the error recovery status of the expectToken function func TestExpectTokenWithRecovery(t *testing.T) { lexer := NewLexer("FROM SELECT") parser := &Parser{lexer: lexer} parser.errorRecovery = NewErrorRecovery(parser) - // 第一次调用应该失败 + // The first call should fail _, err := parser.expectToken(TokenSELECT, "test context") if err == nil { t.Error("Expected error on first call") } } -// TestParseWithMultipleErrors 测试Parse函数处理多个错误的情况 +// TestParseWithMultipleErrors tests when the Parse function handles multiple errors func TestParseWithMultipleErrors(t *testing.T) { - // 创建一个有多个语法错误的查询 + // Create a query with multiple syntax errors parser := NewParser("SELECT FROM WHERE GROUP") stmt, err := parser.Parse() if err == nil { @@ -119,7 +119,7 @@ func TestParseWithMultipleErrors(t *testing.T) { } } -// TestIsIdentifier 测试标识符验证函数 +// TestIsIdentifier is a test identifier verification function func TestIsIdentifier(t *testing.T) { tests := []struct { name string @@ -150,7 +150,7 @@ func TestIsIdentifier(t *testing.T) { } } -// TestExtractSimpleField 测试简单字段提取函数 +// TestExtractSimpleField tests the simple field extraction function func TestExtractSimpleField(t *testing.T) { tests := []struct { name string @@ -177,7 +177,7 @@ func TestExtractSimpleField(t *testing.T) { } } -// TestParseWindowParams 测试窗口参数解析函数 +// TestParseWindowParams is a parameter parsing function for the test window func TestParseWindowParams(t *testing.T) { tests := []struct { name string @@ -219,7 +219,7 @@ func TestParseWindowParams(t *testing.T) { name: "非字符串参数", params: []any{123}, windowType: "TUMBLINGWINDOW", - expectError: false, // 整数参数会被视为秒数,这是有效的 + expectError: false, // The integer parameter is considered a second, which is valid }, { name: "空参数", @@ -265,7 +265,7 @@ func TestParseWindowParams(t *testing.T) { } } -// TestParseAggregateExpression 测试聚合表达式解析函数 +// TestParseAggregateExpression tests the parsing function of aggregate expressions func TestParseAggregateExpression(t *testing.T) { tests := []struct { name string @@ -293,9 +293,9 @@ func TestParseAggregateExpression(t *testing.T) { } } -// TestExpectToken 测试期望token函数 +// TestExpectToken tests the ExpectToken function func TestExpectToken(t *testing.T) { - // 测试正常情况 + // Test for normal conditions parser := NewParser("SELECT field FROM table") tok, err := parser.expectToken(TokenSELECT, "SELECT clause") if err != nil { @@ -305,20 +305,20 @@ func TestExpectToken(t *testing.T) { t.Errorf("Expected SELECT token, got %v", tok.Type) } - // 直接测试getTokenTypeName函数 + // Directly test the getTokenTypeName function result := parser.getTokenTypeName(TokenSELECT) if result != "SELECT" { t.Errorf("Expected 'SELECT', got %v", result) } - // 测试getTokenTypeName的其他分支 + // Test other branches of getTokenTypeName result2 := parser.getTokenTypeName(TokenType(999)) if result2 != "unknown" { t.Errorf("Expected 'unknown', got %v", result2) } } -// TestGetTokenTypeName 测试获取token类型名称函数 +// TestGetTokenTypeName Tests the function to get the token typename func TestGetTokenTypeName(t *testing.T) { parser := NewParser("") tests := []struct { @@ -337,7 +337,7 @@ func TestGetTokenTypeName(t *testing.T) { {TokenQuotedIdent, "quoted identifier"}, {TokenNumber, "number"}, {TokenString, "string"}, - {TokenType(999), "unknown"}, // 未知类型 + {TokenType(999), "unknown"}, // Unknown type } for _, tt := range tests { @@ -350,20 +350,20 @@ func TestGetTokenTypeName(t *testing.T) { } } -// TestSkipToNextDelimiter 测试跳转到下一个分隔符函数 +// TestSkipToNextDelimiter tests and jumps to the next delimiter function func TestSkipToNextDelimiter(t *testing.T) { - // 测试正常跳转 + // Test the normal jump parser := NewParser("field1, field2 FROM table") er := NewErrorRecovery(parser) er.parser = parser - // 跳过到逗号 + // Skip to the comma success := er.skipToNextDelimiter() if !success { t.Error("Expected successful skip to delimiter") } - // 测试到达EOF + // The test reached EOF parser2 := NewParser("field1 field2") er2 := NewErrorRecovery(parser2) er2.parser = parser2 @@ -374,7 +374,7 @@ func TestSkipToNextDelimiter(t *testing.T) { } } -// TestCreateSemanticError 测试创建语义错误函数 +// TestCreateSemanticError tests the semantic error function func TestCreateSemanticError(t *testing.T) { err := CreateSemanticError("Invalid field reference", 10) if err == nil { @@ -391,7 +391,7 @@ func TestCreateSemanticError(t *testing.T) { } } -// TestFormatErrorContext 测试格式化错误上下文函数 +// TestFormatErrorContext Tests the formatting error context function func TestFormatErrorContext(t *testing.T) { input := "SELECT field FROM table WHERE condition" position := 10 @@ -402,7 +402,7 @@ func TestFormatErrorContext(t *testing.T) { t.Error("Expected non-empty error context") } - // 测试边界情况 + // Test boundary conditions result2 := FormatErrorContext("", 0, 0) if result2 != "" { t.Error("Expected empty result for empty input") @@ -414,7 +414,7 @@ func TestFormatErrorContext(t *testing.T) { } } -// TestConvertValue 测试值转换函数 +// TestConvertValue Converts the test value conversion function func TestConvertValue(t *testing.T) { tests := []struct { input string @@ -440,9 +440,9 @@ func TestConvertValue(t *testing.T) { } } -// TestHandleLimitToken 测试处理LIMIT token函数 +// TestHandleLimitToken tests the processing LIMIT token function func TestHandleLimitToken(t *testing.T) { - // 测试正常情况 + // Test for normal conditions parser := NewParser("10") stmt := &SelectStatement{} limitToken := Token{Type: TokenLIMIT, Value: "LIMIT"} @@ -455,7 +455,7 @@ func TestHandleLimitToken(t *testing.T) { t.Errorf("Expected limit 10, got %v", stmt.Limit) } - // 测试无效LIMIT值 + // Test invalid LIMIT value parser2 := NewParser("invalid") stmt2 := &SelectStatement{} err = parser2.handleLimitToken(stmt2, limitToken) @@ -463,7 +463,7 @@ func TestHandleLimitToken(t *testing.T) { t.Error("Expected error for invalid limit value") } - // 测试负数LIMIT值 + // Test the negative LIMIT value parser3 := NewParser("-5") stmt3 := &SelectStatement{} err = parser3.handleLimitToken(stmt3, limitToken) @@ -471,7 +471,7 @@ func TestHandleLimitToken(t *testing.T) { t.Error("Expected error for negative limit value") } - // 测试减号后跟非数字 + // Test minus signs followed by non-numbers parser4 := NewParser("- abc") stmt4 := &SelectStatement{} err = parser4.handleLimitToken(stmt4, limitToken) @@ -479,7 +479,7 @@ func TestHandleLimitToken(t *testing.T) { t.Error("Expected error for minus followed by non-number") } - // 测试减号后跟数字(负数情况) + // Test the minus sign followed by the number (negative number case) parser5 := NewParser("- 10") stmt5 := &SelectStatement{} err = parser5.handleLimitToken(stmt5, limitToken) @@ -488,18 +488,18 @@ func TestHandleLimitToken(t *testing.T) { } } -// TestReadString 测试读取字符串函数 +// TestReadString tests the string function to be read func TestReadString(t *testing.T) { - // 测试正常字符串 + // Test the normal string lexer := NewLexer("'hello world'") - lexer.readChar() // 跳过开始的引号 + lexer.readChar() // Skip the quotation marks at the beginning result := lexer.readString() if result != "hello world'" { t.Errorf("Expected 'hello world', got %v", result) } - // 测试未闭合的字符串 + // Test the unclosed string lexer2 := NewLexer("'unclosed string") lexer2.readChar() @@ -529,9 +529,9 @@ func TestParseLimitNotSubstring(t *testing.T) { } } -// TestParseLimit 测试解析LIMIT子句函数 +// TestParseLimit tests and parses the LIMIT clause function func TestParseLimit(t *testing.T) { - // 测试正常LIMIT + // Test normal LIMIT parser := NewParser("SELECT * FROM table LIMIT 10") stmt := &SelectStatement{} err := parser.parseLimit(stmt) @@ -542,7 +542,7 @@ func TestParseLimit(t *testing.T) { t.Errorf("Expected limit 10, got %v", stmt.Limit) } - // 测试没有LIMIT子句 + // The test lacks a LIMIT clause parser2 := NewParser("SELECT * FROM table") stmt2 := &SelectStatement{} err = parser2.parseLimit(stmt2) @@ -553,7 +553,7 @@ func TestParseLimit(t *testing.T) { t.Errorf("Expected limit 0, got %v", stmt2.Limit) } - // 测试LIMIT后没有数字 + // After testing LIMIT, there are no numbers parser3 := NewParser("SELECT * FROM table LIMIT") stmt3 := &SelectStatement{} err = parser3.parseLimit(stmt3) @@ -561,7 +561,7 @@ func TestParseLimit(t *testing.T) { t.Error("Expected error for LIMIT without number") } - // 测试LIMIT后跟无效值 + // Test the invalid value after the LIMIT parser4 := NewParser("SELECT * FROM table LIMIT abc") stmt4 := &SelectStatement{} err = parser4.parseLimit(stmt4) @@ -569,7 +569,7 @@ func TestParseLimit(t *testing.T) { t.Error("Expected error for invalid LIMIT value") } - // 测试LIMIT负数 + // Test LIMIT for negative numbers parser5 := NewParser("SELECT * FROM table LIMIT -5") stmt5 := &SelectStatement{} err = parser5.parseLimit(stmt5) @@ -577,7 +577,7 @@ func TestParseLimit(t *testing.T) { t.Error("Expected error for negative LIMIT") } - // 测试已设置LIMIT的情况 + // Test when a LIMIT has been set parser6 := NewParser("SELECT * FROM table LIMIT 20") stmt6 := &SelectStatement{Limit: 15} err = parser6.parseLimit(stmt6) @@ -589,9 +589,9 @@ func TestParseLimit(t *testing.T) { } } -// TestParseHaving 测试解析HAVING子句函数 +// TestParseHaving tests and parses the HAVING HAVING clause function func TestParseHaving(t *testing.T) { - // 测试正常HAVING子句 + // Test the normal HAVING clause parser := NewParser("SELECT COUNT(*) FROM table GROUP BY id HAVING COUNT(*) > 5") stmt := &SelectStatement{} err := parser.parseHaving(stmt) @@ -599,7 +599,7 @@ func TestParseHaving(t *testing.T) { t.Errorf("Expected no error, got %v", err) } - // 测试没有HAVING子句 + // The test lacks a HAVING clause parser2 := NewParser("SELECT * FROM table") stmt2 := &SelectStatement{} err = parser2.parseHaving(stmt2) @@ -607,7 +607,7 @@ func TestParseHaving(t *testing.T) { t.Errorf("Expected no error for missing HAVING, got %v", err) } - // 测试HAVING子句中的各种条件 + // Test various conditions in the HAVING clause parser3 := NewParser("HAVING field = 'value' AND count > 10 OR status LIKE 'active%'") stmt3 := &SelectStatement{} err = parser3.parseHaving(stmt3) @@ -615,7 +615,7 @@ func TestParseHaving(t *testing.T) { t.Errorf("Expected no error for complex HAVING, got %v", err) } - // 测试HAVING后遇到LIMIT + // After testing HAVING, I encountered LIMIT parser4 := NewParser("HAVING count > 5 LIMIT 10") stmt4 := &SelectStatement{} err = parser4.parseHaving(stmt4) @@ -623,7 +623,7 @@ func TestParseHaving(t *testing.T) { t.Errorf("Expected no error when HAVING followed by LIMIT, got %v", err) } - // 测试HAVING后遇到WITH + // After testing HAVING, I encountered WITH parser5 := NewParser("HAVING count > 5 WITH TUMBLING") stmt5 := &SelectStatement{} err = parser5.parseHaving(stmt5) @@ -632,9 +632,9 @@ func TestParseHaving(t *testing.T) { } } -// TestParseWithErrorRecovery 测试Parse函数的错误恢复 +// TestParseWithErrorRecovery: Error recovery for the parse function func TestParseWithErrorRecovery(t *testing.T) { - // 测试基本的错误情况 + // Testing basic error scenarios parser := NewParser("SELECT * FROM table WHERE id = 1") stmt, err := parser.Parse() if err != nil { @@ -644,10 +644,10 @@ func TestParseWithErrorRecovery(t *testing.T) { t.Error("Expected statement for valid syntax") } - // 测试错误恢复后的继续解析 + // Continue parsing after error recovery during testing parser2 := NewParser("SELECT * FROM table GROUP BY field") stmt2, err := parser2.Parse() - // 这应该是有效的语法 + // This should be effective grammar if err != nil { t.Errorf("Unexpected error for valid GROUP BY: %v", err) } @@ -655,7 +655,7 @@ func TestParseWithErrorRecovery(t *testing.T) { t.Error("Expected statement for valid syntax") } - // 测试完全无效的语法 + // Testing completely ineffective grammar parser3 := NewParser("COMPLETELY INVALID SYNTAX") _, err = parser3.Parse() if err == nil { @@ -663,7 +663,7 @@ func TestParseWithErrorRecovery(t *testing.T) { } } -// TestGenerateFunctionSuggestions 测试生成函数建议函数 +// TestGenerateFunctionSuggestions Test-generated function Suggestions function func TestGenerateFunctionSuggestions(t *testing.T) { tests := []struct { functionName string @@ -691,7 +691,7 @@ func TestGenerateFunctionSuggestions(t *testing.T) { } } -// TestBuildSelectFieldsWithExpressions 测试带表达式的选择字段构建函数 +// TestBuildSelectFieldsWithExpressions tests the selection field constructor with expressions func TestBuildSelectFieldsWithExpressions(t *testing.T) { tests := []struct { name string @@ -754,7 +754,7 @@ func TestBuildSelectFieldsWithExpressions(t *testing.T) { } } -// TestParserLexerErrorHandling 测试词法分析器错误处理 +// TestParserLexerErrorHandling Error handling in the test lexer analyzer func TestParserLexerErrorHandling(t *testing.T) { tests := []struct { name string @@ -777,7 +777,7 @@ func TestParserLexerErrorHandling(t *testing.T) { { name: "无效数字格式", input: "SELECT 123abc FROM table", - expectError: false, // 解析为数字123和标识符abc + expectError: false, // Interpreted as the number 123 and the identifier ABC }, { name: "反引号不匹配", @@ -811,7 +811,7 @@ func TestParserLexerErrorHandling(t *testing.T) { er := NewErrorRecovery(nil) lexer.SetErrorRecovery(er) - // 读取所有token + // Read all tokens for { token := lexer.NextToken() if token.Type == TokenEOF { @@ -830,8 +830,8 @@ func TestParserLexerErrorHandling(t *testing.T) { } } -// TestParserErrorRecoveryCases 测试解析器错误恢复 -// 注意:当前解析器的错误恢复机制可能与预期不同 +// TestParserErrorRecoveryCases test parser error recovery +// Note: The current parser error recovery mechanism may differ from what is expected func TestParserErrorRecoveryCases(t *testing.T) { tests := []struct { name string @@ -841,7 +841,7 @@ func TestParserErrorRecoveryCases(t *testing.T) { }{ { name: "语法错误恢复", - query: "SELECT FROM table WHERE", // 缺少列名 + query: "SELECT FROM table WHERE", // Missing a list expectError: true, shouldParse: false, }, @@ -854,13 +854,13 @@ func TestParserErrorRecoveryCases(t *testing.T) { { name: "WHERE条件不完整", query: "SELECT * FROM table WHERE field >", - expectError: false, // 解析器可能将其作为表达式处理 + expectError: false, // The parser may treat it as an expression shouldParse: true, }, { name: "括号不匹配表达式", query: "SELECT func(field FROM table", - expectError: true, // 解析器检测到语法错误 + expectError: true, // The parser detects syntax errors shouldParse: false, }, { @@ -879,12 +879,12 @@ func TestParserErrorRecoveryCases(t *testing.T) { name: "长标识符", query: "SELECT " + strings.Repeat("a", 100) + " FROM table", expectError: false, - shouldParse: true, // 解析器支持长标识符 + shouldParse: true, // The parser supports long identifiers }, { name: "子查询语法", query: "SELECT column FROM (SELECT * FROM table) AS sub", - expectError: true, // 当前解析器不支持子查询 + expectError: true, // The current parser does not support subqueries shouldParse: false, }, } @@ -898,7 +898,7 @@ func TestParserErrorRecoveryCases(t *testing.T) { t.Errorf("expected error: %v, got: %v", tt.expectError, err) } - // 验证是否能继续解析 + // Verify whether further analysis is possible if !tt.shouldParse && err == nil { t.Errorf("expected parsing to fail, but it succeeded") } @@ -906,8 +906,8 @@ func TestParserErrorRecoveryCases(t *testing.T) { } } -// TestParserCaseExpressionErrors 测试CASE表达式错误处理 -// 注意:当前解析器将CASE表达式作为普通表达式处理,不进行特殊语法验证 +// TestParserCaseExpressionErrors Tests CASE expression error handling +// Note: The current parser treats CASE expressions as regular expressions and does not perform special syntax verification func TestParserCaseExpressionErrors(t *testing.T) { tests := []struct { name string @@ -917,7 +917,7 @@ func TestParserCaseExpressionErrors(t *testing.T) { { name: "CASE作为表达式", query: "SELECT CASE WHEN condition THEN value END FROM table", - expectError: false, // 当前解析器将其作为表达式处理 + expectError: false, // The current parser treats it as an expression }, { name: "简单CASE表达式", @@ -937,7 +937,7 @@ func TestParserCaseExpressionErrors(t *testing.T) { { name: "CASE表达式语法错误", query: "SELECT CASE WHEN value > 10 AND < 20 THEN 'A' END FROM table", - expectError: false, // 解析器不验证表达式内部语法 + expectError: false, // The parser does not verify the internal syntax of the expression }, } @@ -953,8 +953,8 @@ func TestParserCaseExpressionErrors(t *testing.T) { } } -// TestParserComplexFieldAccess 测试复杂字段访问错误处理 -// 注意:当前解析器将复杂字段访问作为表达式处理 +// TestParserComplexFieldAccess tests complex field access error handling +// Note: The current parser treats complex field access as an expression func TestParserComplexFieldAccess(t *testing.T) { tests := []struct { name string @@ -964,7 +964,7 @@ func TestParserComplexFieldAccess(t *testing.T) { { name: "数组索引语法错误", query: "SELECT field[ FROM table", - expectError: false, // 解析器将其作为表达式处理 + expectError: false, // The parser treats it as an expression }, { name: "嵌套数组访问", @@ -989,7 +989,7 @@ func TestParserComplexFieldAccess(t *testing.T) { { name: "混合访问表达式", query: "SELECT field.nested[0].deep FROM table", - expectError: false, // lexer现已支持点号在表达式中 + expectError: false, // Lexer now supports dot marks in expressions }, { name: "标识符数组索引", @@ -999,12 +999,12 @@ func TestParserComplexFieldAccess(t *testing.T) { { name: "未闭合括号", query: "SELECT field[0 FROM table", - expectError: false, // 解析器将其作为表达式处理 + expectError: false, // The parser treats it as an expression }, { name: "空数组索引", query: "SELECT field[] FROM table", - expectError: false, // 解析器将其作为表达式处理 + expectError: false, // The parser treats it as an expression }, } @@ -1020,7 +1020,7 @@ func TestParserComplexFieldAccess(t *testing.T) { } } -// TestParserBoundaryConditions 测试解析器边界条件 +// TestParserBoundaryConditions tests the parser boundary conditions func TestParserBoundaryConditions(t *testing.T) { tests := []struct { name string @@ -1035,7 +1035,7 @@ func TestParserBoundaryConditions(t *testing.T) { { name: "大量字段", query: "SELECT " + strings.Repeat("col,", 50) + "last FROM table", - expectError: false, // 解析器实际上没有字段数量限制 + expectError: false, // The parser actually has no field limit }, { name: "复杂WHERE条件", diff --git a/rsql/error_test.go b/rsql/error_test.go index e7de2b4..e3c8c71 100644 --- a/rsql/error_test.go +++ b/rsql/error_test.go @@ -6,7 +6,7 @@ import ( "testing" ) -// TestParseError 测试 ParseError 结构体 +// TestParseError Tests the ParseError structure func TestParseError(t *testing.T) { err := &ParseError{ Type: ErrorTypeSyntax, @@ -21,7 +21,7 @@ func TestParseError(t *testing.T) { Recoverable: true, } - // 测试 Error() 方法 + // Test the Error() method errorStr := err.Error() if !strings.Contains(errorStr, "SYNTAX_ERROR") { t.Errorf("Error string should contain 'SYNTAX_ERROR', got: %s", errorStr) @@ -45,13 +45,13 @@ func TestParseError(t *testing.T) { t.Errorf("Error string should contain suggestions, got: %s", errorStr) } - // 测试 IsRecoverable() 方法 + // Test the IsRecoverable() method if !err.IsRecoverable() { t.Error("Error should be recoverable") } } -// TestEnhancedErrorHandling 测试增强的错误处理 +// TestEnhancedErrorHandling tests enhanced error handling func TestEnhancedErrorHandling(t *testing.T) { tests := []struct { name string @@ -100,13 +100,13 @@ func TestEnhancedErrorHandling(t *testing.T) { parser := NewParser(test.input) _, err := parser.Parse() - // 应该有错误 + // There should be errors if err == nil && !parser.HasErrors() { t.Errorf("Expected error but got none") return } - // 检查错误数量 + // Check the number of errors if test.expectedErrors > 0 { errors := parser.GetErrors() if len(errors) != test.expectedErrors { @@ -114,7 +114,7 @@ func TestEnhancedErrorHandling(t *testing.T) { } } - // 检查错误内容 + // Check for errors if test.contains != "" { errorFound := false for _, parseErr := range parser.GetErrors() { @@ -131,7 +131,7 @@ func TestEnhancedErrorHandling(t *testing.T) { } } -// TestErrorTypes 测试错误类型 +// TestErrorTypes Test error types func TestErrorTypes(t *testing.T) { errorTypes := []ErrorType{ ErrorTypeSyntax, @@ -161,7 +161,7 @@ func TestErrorTypes(t *testing.T) { } } -// TestErrorRecovery 测试错误恢复机制 +// TestErrorRecovery: Test error recovery mechanism func TestErrorRecovery(t *testing.T) { tests := []struct { name string @@ -207,7 +207,7 @@ func TestErrorRecovery(t *testing.T) { } } -// TestNewFunctionValidator 测试 FunctionValidator 创建 +// TestNewFunctionValidator Test FunctionValidator created func TestNewFunctionValidator(t *testing.T) { lexer := NewLexer("SELECT * FROM table") parser := &Parser{lexer: lexer} @@ -224,7 +224,7 @@ func TestNewFunctionValidator(t *testing.T) { } } -// TestFunctionValidatorValidateExpression 测试函数验证器的表达式验证 +// TestFunctionValidatorValidateExpression Validates the expression of the test function validator func TestFunctionValidatorValidateExpression(t *testing.T) { tests := []struct { name string @@ -298,14 +298,14 @@ func TestFunctionValidatorValidateExpression(t *testing.T) { } } -// TestFunctionValidatorBuiltins 测试函数验证器内置函数 +// TestFunctionValidatorBuiltins Test function Verifier built-in function func TestFunctionValidatorBuiltins(t *testing.T) { lexer := NewLexer("SELECT * FROM table") parser := &Parser{lexer: lexer} er := NewErrorRecovery(parser) validator := NewFunctionValidator(er) - // 测试内置函数验证(基于实际实现的数学函数) + // Testing built-in function validation (mathematical functions based on actual implementation) builtinFunctions := []string{"ABS", "ROUND", "SQRT", "SIN", "COS", "FLOOR", "CEIL"} for _, funcName := range builtinFunctions { t.Run("Builtin_"+funcName, func(t *testing.T) { @@ -315,18 +315,18 @@ func TestFunctionValidatorBuiltins(t *testing.T) { }) } - // 测试聚合函数(这些在函数注册系统中是有效的) + // Test aggregate functions (these are valid in function registration systems) aggregateFunctions := []string{"COUNT", "SUM", "AVG", "MAX", "MIN"} for _, funcName := range aggregateFunctions { t.Run("Aggregate_"+funcName, func(t *testing.T) { - // 聚合函数应该在函数注册系统中存在 + // Aggregate functions should exist in the function registration system if !validator.isBuiltinFunction(funcName) { t.Errorf("Expected %s to be a valid function (it's registered in the function registry)", funcName) } }) } - // 测试无效函数 + // Test the invalid function invalidFunctions := []string{"INVALID_FUNC", "UNKNOWN", ""} for _, funcName := range invalidFunctions { t.Run("Invalid_"+funcName, func(t *testing.T) { diff --git a/rsql/function_validator.go b/rsql/function_validator.go index 8d1a07b..905d91a 100644 --- a/rsql/function_validator.go +++ b/rsql/function_validator.go @@ -77,9 +77,9 @@ func (fv *FunctionValidator) extractFunctionCalls(expression string) []FunctionC } // isBuiltinFunction checks if it's a built-in function using the unified function registry -// 使用统一的函数注册系统检查函数是否存在 +// Use a unified function registration system to check if a function exists func (fv *FunctionValidator) isBuiltinFunction(funcName string) bool { - // 检查函数是否在统一的函数注册系统中 + // Check whether the function is registered in a unified function registration system _, exists := functions.Get(strings.ToLower(funcName)) return exists } diff --git a/rsql/lexer.go b/rsql/lexer.go index 9c72dc4..fee6909 100644 --- a/rsql/lexer.go +++ b/rsql/lexer.go @@ -12,7 +12,7 @@ const ( TokenIdent TokenNumber TokenString - TokenQuotedIdent // 反引号标识符 + TokenQuotedIdent // Backquote identifier TokenComma TokenLParen TokenRParen @@ -56,21 +56,21 @@ const ( TokenIS TokenNULL TokenNOT - // CASE表达式相关token + // CASE expression-related tokens TokenCASE TokenWHEN TokenTHEN TokenELSE TokenEND - // 数组索引相关token + // Array indexes related tokens TokenLBracket TokenRBracket - // 分析函数 OVER 子句相关 token + // Analyze the token related to the OVER clause of the function TokenOVER TokenPARTITION - // 点号token + // Point token TokenDot - // MATCH_RECOGNIZE PATTERN 正则语法标点(仅 PATTERN 体内消费) + // MATCH_RECOGNIZE PATTERN regular syntax punctuation (PATTERN internal consumption only) TokenQuestion // ? TokenPipe // | TokenLBrace // { @@ -105,12 +105,12 @@ func NewLexer(input string) *Lexer { return l } -// SetErrorRecovery 设置错误恢复实例 +// SetErrorRecovery sets the error recovery instance func (l *Lexer) SetErrorRecovery(er *ErrorRecovery) { l.errorRecovery = er } -// GetPosition 获取当前位置信息 +// GetPosition retrieves the current location information func (l *Lexer) GetPosition() (int, int, int) { return l.pos, l.line, l.column } @@ -139,7 +139,7 @@ func (l *Lexer) restore(s lexerSnapshot) { func (l *Lexer) NextToken() Token { l.skipWhitespace() - // 记录token开始位置 + // Record the starting position of the token tokenPos := l.pos tokenLine := l.line tokenColumn := l.column @@ -181,12 +181,12 @@ func (l *Lexer) NextToken() Token { l.readChar() return Token{Type: TokenPlus, Value: "+", Pos: tokenPos, Line: tokenLine, Column: tokenColumn} case '-': - // 检查是否是负数 + // Check if it's negative if l.peekChar() != 0 && isDigit(l.peekChar()) { - // 这是一个负数,读取整个数字 - l.readChar() // 跳过负号 + // This is a negative number, read the entire number + l.readChar() // Skip the minus sign number := "-" + l.readNumber() - // 验证数字格式 + // Verify the digital format if !l.isValidNumber(number) && l.errorRecovery != nil { err := CreateLexicalError(fmt.Sprintf("Invalid number format: %s", number), tokenPos, 0) err.Type = ErrorTypeInvalidNumber @@ -194,7 +194,7 @@ func (l *Lexer) NextToken() Token { } return Token{Type: TokenNumber, Value: number, Pos: tokenPos, Line: tokenLine, Column: tokenColumn} } - // 这是一个减号操作符 + // This is a minus operator l.readChar() return Token{Type: TokenMinus, Value: "-", Pos: tokenPos, Line: tokenLine, Column: tokenColumn} case '*': @@ -233,13 +233,13 @@ func (l *Lexer) NextToken() Token { l.readChar() return Token{Type: TokenNE, Value: "!=", Pos: tokenPos, Line: tokenLine, Column: tokenColumn} } - // 处理无效的 '!' 字符 + // Handle invalid '!' characters if l.errorRecovery != nil { err := CreateLexicalErrorWithPosition("Invalid character '!', did you mean '!='?", tokenPos, tokenLine, tokenColumn, l.ch) l.errorRecovery.AddError(err) } l.readChar() - return l.NextToken() // 跳过无效字符,继续解析 + return l.NextToken() // Skip invalid characters and continue parsing case '\'': return l.readStringToken(tokenPos, tokenLine, tokenColumn) case '"': @@ -259,7 +259,7 @@ func (l *Lexer) NextToken() Token { if isDigit(l.ch) { number := l.readNumber() - // 验证数字格式 + // Verify the digital format if !l.isValidNumber(number) && l.errorRecovery != nil { err := CreateLexicalError(fmt.Sprintf("Invalid number format: %s", number), tokenPos, 0) err.Type = ErrorTypeInvalidNumber @@ -268,14 +268,14 @@ func (l *Lexer) NextToken() Token { return Token{Type: TokenNumber, Value: number, Pos: tokenPos, Line: tokenLine, Column: tokenColumn} } - // 处理无法识别的字符 + // Handling unrecognizable characters if l.ch != 0 { if l.errorRecovery != nil { err := CreateLexicalErrorWithPosition(fmt.Sprintf("Unexpected character '%c'", l.ch), tokenPos, tokenLine, tokenColumn, l.ch) l.errorRecovery.AddError(err) } l.readChar() - return l.NextToken() // 跳过无效字符,继续解析 + return l.NextToken() // Skip invalid characters and continue parsing } return Token{Type: TokenEOF, Pos: tokenPos, Line: tokenLine, Column: tokenColumn} @@ -288,7 +288,7 @@ func (l *Lexer) readChar() { l.ch = l.input[l.readPos] } - // 更新位置信息 + // Update location information if l.ch == '\n' { l.line++ l.column = 0 @@ -318,7 +318,7 @@ func (l *Lexer) readIdentifier() string { } func (l *Lexer) readPreviousIdentifier() string { - // 保存当前位置 + // Save your current location endPos := l.pos // Move backward until finding a non-letter character or reaching the input start @@ -349,14 +349,14 @@ func (l *Lexer) readNumber() string { func (l *Lexer) readString() string { quoteChar := l.ch // Record the quote type (single or double quote) startPos := l.pos // Record the start position (including the quote) - l.readChar() // 跳过开头引号 + l.readChar() // Skip the opening quotation marks for l.ch != quoteChar && l.ch != 0 { l.readChar() } if l.ch == quoteChar { - l.readChar() // 跳过结尾引号 + l.readChar() // Skip the closing quotes } // Return the complete string including quotes @@ -432,7 +432,7 @@ func (l *Lexer) lookupIdent(ident string) Token { return Token{Type: TokenNULL, Value: ident} case "NOT": return Token{Type: TokenNOT, Value: ident} - // CASE表达式相关关键字 + // CASE expression-related keywords case "CASE": return Token{Type: TokenCASE, Value: ident} case "WHEN": @@ -456,7 +456,7 @@ func (l *Lexer) lookupIdent(ident string) Token { } } -// checkForTypos 检查常见的拼写错误 +// checkForTypos checks for common spelling errors func (l *Lexer) checkForTypos(original, upper string) { suggestions := make([]string, 0) @@ -490,11 +490,11 @@ func (l *Lexer) checkForTypos(original, upper string) { } } -// readStringToken 读取字符串token并处理错误 +// readStringToken reads the string token and handles the error func (l *Lexer) readStringToken(pos, line, column int) Token { quoteChar := l.ch startPos := l.pos - l.readChar() // 跳过开头引号 + l.readChar() // Skip the opening quotation marks for l.ch != quoteChar && l.ch != 0 { l.readChar() @@ -520,17 +520,17 @@ func (l *Lexer) readStringToken(pos, line, column int) Token { } if l.ch == quoteChar { - l.readChar() // 跳过结尾引号 + l.readChar() // Skip the closing quotes } value := l.input[startPos:l.pos] return Token{Type: TokenString, Value: value, Pos: pos, Line: line, Column: column} } -// readQuotedIdentToken 读取反引号标识符token并处理错误 +// readQuotedIdentToken reads the backquoted identifier token and handles the error func (l *Lexer) readQuotedIdentToken(pos, line, column int) Token { startPos := l.pos - l.readChar() // 跳过开头反引号 + l.readChar() // Skip the opening backquotes for l.ch != '`' && l.ch != 0 { l.readChar() @@ -556,24 +556,24 @@ func (l *Lexer) readQuotedIdentToken(pos, line, column int) Token { } if l.ch == '`' { - l.readChar() // 跳过结尾反引号 + l.readChar() // Skip the closing quotation marks } value := l.input[startPos:l.pos] return Token{Type: TokenQuotedIdent, Value: value, Pos: pos, Line: line, Column: column} } -// isValidNumber 验证数字格式 +// isValidNumber verifies the numeric format func (l *Lexer) isValidNumber(number string) bool { if number == "" { return false } - // 处理负数 + // Handle negative numbers startIndex := 0 if number[0] == '-' { if len(number) == 1 { - return false // 只有负号 + return false // Only the minus sign } startIndex = 1 } @@ -584,14 +584,14 @@ func (l *Lexer) isValidNumber(number string) bool { if ch == '.' { dotCount++ if dotCount > 1 { - return false // 多个小数点 + return false // Multiple decimal points } } else if !isDigit(ch) { - return false // 非数字字符 + return false // Non-numeric characters } } - // 检查是否以小数点开头或结尾 + // Check whether it starts or ends with a decimal point if number[startIndex] == '.' || number[len(number)-1] == '.' { return false } diff --git a/rsql/lexer_test.go b/rsql/lexer_test.go index 9d50a3d..6d7a5f1 100644 --- a/rsql/lexer_test.go +++ b/rsql/lexer_test.go @@ -6,7 +6,7 @@ import ( "github.com/stretchr/testify/assert" ) -// TestNewLexer 测试词法分析器的创建 +// TestNewLexer creates a test lexical analyzer func TestNewLexer(t *testing.T) { input := "SELECT * FROM table" lexer := NewLexer(input) @@ -28,7 +28,7 @@ func TestNewLexer(t *testing.T) { } } -// TestLexerBasicTokens 测试基本token的识别 +// TestLexerBasicTokens tests the recognition of basic tokens func TestLexerBasicTokens(t *testing.T) { tests := []struct { input string @@ -64,7 +64,7 @@ func TestLexerBasicTokens(t *testing.T) { } } -// TestQuotedIdentifiers 测试反引号标识符的词法分析 +// TestQuotedIdentifiers tests the lexical analysis of backquoted identifiers func TestQuotedIdentifiers(t *testing.T) { t.Run("基本反引号标识符", func(t *testing.T) { lexer := NewLexer("`deviceId`") @@ -100,7 +100,7 @@ func TestQuotedIdentifiers(t *testing.T) { }) } -// TestStringLiterals 测试字符串常量的词法分析 +// TestStringLiterals: Lexical analysis of test string constants func TestStringLiterals(t *testing.T) { t.Run("单引号字符串", func(t *testing.T) { lexer := NewLexer("'hello world'") @@ -129,7 +129,7 @@ func TestStringLiterals(t *testing.T) { }) } -// TestLexerErrorHandling 测试词法分析器错误处理 +// TestLexerErrorHandling Error handling in the test lexer analyzer func TestLexerErrorHandling(t *testing.T) { tests := []struct { name string @@ -148,7 +148,7 @@ func TestLexerErrorHandling(t *testing.T) { errorRecovery := NewErrorRecovery(nil) lexer.SetErrorRecovery(errorRecovery) - // 读取所有token直到EOF + // Read all tokens up to EOF for { token := lexer.NextToken() if token.Type == TokenEOF { @@ -156,21 +156,21 @@ func TestLexerErrorHandling(t *testing.T) { } } - // 应该有错误 + // There should be errors if !errorRecovery.HasErrors() { t.Errorf("Expected errors for input: %s", test.input) } }) } - // 测试词法分析器的位置获取 + // Test lexical analyzer location acquisition lexer := NewLexer("SELECT * FROM table") pos, line, column := lexer.GetPosition() if pos < 0 || line < 1 || column < 0 { t.Errorf("Invalid position: pos=%d, line=%d, column=%d", pos, line, column) } - // 测试词法分析器的位置跟踪 + // Test the lexical analyzer's position tracking lexer = NewLexer("SELECT\n *\nFROM\n table") // SELECT @@ -198,7 +198,7 @@ func TestLexerErrorHandling(t *testing.T) { } } -// TestLexerOperators 测试操作符的词法分析 +// TestLexerOperators tests the lexical analysis of operators func TestLexerOperators(t *testing.T) { tests := []struct { input string @@ -233,7 +233,7 @@ func TestLexerOperators(t *testing.T) { } } -// TestLexerNumbers 测试数字的词法分析 +// TestLexerNumbers tests the lexical analysis of numbers func TestLexerNumbers(t *testing.T) { tests := []struct { input string @@ -260,7 +260,7 @@ func TestLexerNumbers(t *testing.T) { } } -// TestLexerIdentifiers 测试标识符的词法分析 +// TestLexerIdentifiers: Lexical analysis of test identifiers func TestLexerIdentifiers(t *testing.T) { tests := []struct { input string @@ -288,9 +288,9 @@ func TestLexerIdentifiers(t *testing.T) { } } -// TestTokenTypes 测试Token类型 +// TestTokenTypes Test Token types func TestTokenTypes(t *testing.T) { - // 测试关键字token + // Test keyword tokens keywordTests := []struct { input string expected TokenType @@ -324,7 +324,7 @@ func TestTokenTypes(t *testing.T) { } } -// TestLexerWhitespace 测试空白字符处理 +// TestLexerWhitespace tests whitespace character handling func TestLexerWhitespace(t *testing.T) { tests := []struct { name string @@ -366,7 +366,7 @@ func TestLexerWhitespace(t *testing.T) { } } -// TestLexerComplexTokens 测试复杂token组合 +// TestLexerComplexTokens Tests complex token combinations func TestLexerComplexTokens(t *testing.T) { tests := []struct { name string diff --git a/rsql/parser.go b/rsql/parser.go index f7bf1e0..69f7daa 100644 --- a/rsql/parser.go +++ b/rsql/parser.go @@ -12,17 +12,17 @@ import ( "github.com/rulego/streamsql/utils/cast" ) -// 解析器配置常量 +// The parser constants are constant const ( - // MaxRecursionDepth 定义 expectTokenWithDepth 方法的最大递归深度 - // 用于防止无限递归 + // MaxRecursionDepth defines the maximum recursive depth of the expectTokenWithDepth method + // Used to prevent infinite recursion MaxRecursionDepth = 30 - // MaxSelectFields 定义 SELECT 子句中允许的最大字段数量 + // MaxSelectFields defines the maximum number of fields allowed in the SELECT clause MaxSelectFields = 300 ) -// tokenTypeNames 定义 token 类型到名称的映射表 +// tokenTypeNames defines the mapping table from token type to name var tokenTypeNames = map[TokenType]string{ TokenSELECT: "SELECT", TokenFROM: "FROM", @@ -70,25 +70,25 @@ func NewParser(input string) *Parser { return p } -// GetErrors 获取解析过程中的所有错误 +// GetErrors retrieves all errors during the parsing process func (p *Parser) GetErrors() []*ParseError { return p.errorRecovery.GetErrors() } -// HasErrors 检查是否有错误 +// HasErrors checks for errors func (p *Parser) HasErrors() bool { return p.errorRecovery.HasErrors() } -// expectToken 期望特定类型的token +// expectToken: Expects a specific type of token func (p *Parser) expectToken(expected TokenType, context string) (Token, error) { return p.expectTokenWithDepth(expected, context, 0) } -// expectTokenWithDepth 期望特定类型的token,带递归深度限制 -// 使用可配置的最大递归深度防止无限递归,提供更好的错误处理和恢复机制 +// expectTokenWithDepth: Expects a specific type of token, with a recursive depth limit +// Uses the maximum configurable recursion depth to prevent infinite recursion, providing better error handling and recovery mechanisms func (p *Parser) expectTokenWithDepth(expected TokenType, context string, depth int) (Token, error) { - // 防止无限递归,使用可配置的最大递归深度 + // Prevents infinite recursion by using the maximum configurable recursion depth if depth > MaxRecursionDepth { tok := p.lexer.NextToken() err := p.createTokenError(tok, expected, context, "maximum recursion depth exceeded") @@ -100,7 +100,7 @@ func (p *Parser) expectTokenWithDepth(expected TokenType, context string, depth err := p.createTokenError(tok, expected, context, "") p.errorRecovery.AddError(err) - // 尝试错误恢复,但限制递归深度 + // Attempts and errors are restored, but recursion depth is limited if p.shouldAttemptRecovery(err, depth) { return p.expectTokenWithDepth(expected, context, depth+1) } @@ -110,8 +110,8 @@ func (p *Parser) expectTokenWithDepth(expected TokenType, context string, depth return tok, nil } -// getTokenTypeName 获取token类型名称 -// 使用映射表提高性能和可维护性 +// getTokenTypeName gets the token type name +// Use mapping tables to improve performance and maintainability func (p *Parser) getTokenTypeName(tokenType TokenType) string { if name, exists := tokenTypeNames[tokenType]; exists { return name @@ -119,8 +119,8 @@ func (p *Parser) getTokenTypeName(tokenType TokenType) string { return "unknown" } -// createTokenError 创建标准化的 token 错误 -// 提供统一的错误创建逻辑,便于维护和扩展 +// createTokenError Creates a standardized token error +// Provides a unified error creation logic for easy maintenance and scalability func (p *Parser) createTokenError(tok Token, expected TokenType, context, additionalInfo string) *ParseError { err := CreateUnexpectedTokenError( tok.Value, @@ -134,37 +134,37 @@ func (p *Parser) createTokenError(tok Token, expected TokenType, context, additi return err } -// shouldAttemptRecovery 判断是否应该尝试错误恢复 -// 基于错误类型和递归深度做出智能决策 +// shouldAttemptRecovery determines whether a false recovery attempt should be made +// Make intelligent decisions based on error types and recursive depth func (p *Parser) shouldAttemptRecovery(err *ParseError, depth int) bool { - // 如果已经接近最大递归深度,不再尝试恢复 + // If the maximum recursive depth is approached, no attempts to recover are made if depth >= MaxRecursionDepth-1 { return false } - // 检查错误是否可恢复,并且错误恢复机制允许恢复 + // Check whether errors can be recovered, and the error recovery mechanism allows for recovery return err.IsRecoverable() && p.errorRecovery.RecoverFromError(ErrorTypeUnexpectedToken) } func (p *Parser) Parse() (*SelectStatement, error) { stmt := &SelectStatement{} - // 解析SELECT子句 - 对于特定的关键错误直接返回 + // Parse the SELECT clause - returns directly for specific critical errors if err := p.parseSelect(stmt); err != nil { - // 检查是否是关键的语法错误,这些错误应该停止进一步解析 + // Check for critical grammatical errors that should be stopped for further parsing if strings.Contains(err.Error(), "Expected SELECT") { - // SELECT关键字错误是致命的,直接返回 + // The SELECT keyword error is fatal and returns directly return nil, p.createDetailedError(err) } - // 检查是否是特定的关键错误模式,这些错误不应该被恢复 - // 只有当查询看起来像 "SELECT FROM table WHERE" 这样的模式时才直接返回错误 + // Check whether there are specific critical error patterns that should not be restored + // Only when the query appears in a pattern like "SELECT FROM table WHERE" does it return an error directly if strings.Contains(err.Error(), "no fields specified") { - // 检查是否有FROM关键字紧跟在SELECT后面 + // Check if the FROM keyword is immediately followed by SELECT nextTok := p.lexer.lookupIdent(p.lexer.readPreviousIdentifier()) if nextTok.Type == TokenFROM { - // 进一步检查:如果后面还有其他内容(如WHERE、GROUP等),则允许错误恢复 - // 只有当查询是简单的 "SELECT FROM table WHERE" 模式时才直接返回错误 + // Further check: If there are other content after it (such as WHERE, GROUP, etc.), error recovery is allowed + // Only when the query is simply in the "SELECT FROM table WHERE" mode will an error be returned directly if !strings.Contains(p.input, "WHERE") || !strings.Contains(p.input, "GROUP") { return nil, p.createDetailedError(err) } @@ -174,45 +174,45 @@ func (p *Parser) Parse() (*SelectStatement, error) { if parseErr, ok := err.(*ParseError); ok { p.errorRecovery.AddError(parseErr) } - // 对于其他错误,继续尝试解析其他部分 + // For other errors, continue to try to parse other parts } - // 解析FROM子句 + // Parsing the FROM clause if err := p.parseFrom(stmt); err != nil { if !p.errorRecovery.RecoverFromError(ErrorTypeSyntax) { return nil, p.createDetailedError(err) } } - // 解析JOIN子句(流-表 JOIN,v0.5) + // Parsing the JOIN clause (stream-table JOIN, v0.5) if err := p.parseJoin(stmt); err != nil { if !p.errorRecovery.RecoverFromError(ErrorTypeSyntax) { return nil, p.createDetailedError(err) } } - // 解析 MATCH_RECOGNIZE 子句(CEP,FROM 后、WHERE 前) + // Parsing MATCH_RECOGNIZE Clause (CEP, after FROM, before WHERE) if err := p.parseMatchRecognize(stmt); err != nil { if !p.errorRecovery.RecoverFromError(ErrorTypeSyntax) { return nil, p.createDetailedError(err) } } - // 解析WHERE子句 + // Parse the WHERE clause if err := p.parseWhere(stmt); err != nil { if !p.errorRecovery.RecoverFromError(ErrorTypeSyntax) { return nil, p.createDetailedError(err) } } - // 解析GROUP BY子句 + // Parse the GROUP BY clause if err := p.parseGroupBy(stmt); err != nil { if !p.errorRecovery.RecoverFromError(ErrorTypeSyntax) { return nil, p.createDetailedError(err) } } - // 解析 HAVING 子句 + // Parse the HAVING clause if err := p.parseHaving(stmt); err != nil { if !p.errorRecovery.RecoverFromError(ErrorTypeSyntax) { return nil, p.createDetailedError(err) @@ -225,21 +225,21 @@ func (p *Parser) Parse() (*SelectStatement, error) { } } - // 解析 ORDER BY 子句 + // Parse the ORDER BY clause if err := p.parseOrderBy(stmt); err != nil { if !p.errorRecovery.RecoverFromError(ErrorTypeSyntax) { return nil, p.createDetailedError(err) } } - // 解析LIMIT子句 + // Parse the LIMIT clause if err := p.parseLimit(stmt); err != nil { if !p.errorRecovery.RecoverFromError(ErrorTypeSyntax) { return nil, p.createDetailedError(err) } } - // 如果有错误但可以恢复,返回部分解析结果和错误信息 + // If there is an error but can be recovered, partial parsing results and error messages are returned if p.errorRecovery.HasErrors() { return stmt, p.createCombinedError() } @@ -247,10 +247,10 @@ func (p *Parser) Parse() (*SelectStatement, error) { return stmt, nil } -// isKeyword 检查给定的字符串是否是SQL关键字 -// 使用预定义的关键字映射表进行快速查找 -// 参数: word - 要检查的字符串 -// 返回: 如果是关键字返回 true,否则返回 false +// isKeyword checks whether the given string is an SQL keyword +// Use predefined keyword mapping tables for quick lookups +// Parameter: word - the string to check +// Return: Returns true if key, otherwise returns false func isKeyword(word string) bool { keywords := map[string]bool{ "SELECT": true, "FROM": true, "WHERE": true, "GROUP": true, "BY": true, @@ -265,10 +265,10 @@ func isKeyword(word string) bool { return keywords[word] } -// createDetailedError 创建详细的错误信息 -// 为 ParseError 类型的错误添加上下文信息,便于调试和错误定位 -// 参数: err - 原始错误 -// 返回: 包含详细上下文信息的错误 +// createDetailedError Creates detailed error information +// Adds contextual information for ParseError-type errors to facilitate debugging and error localization +// Parameter: err - Original error +// Return: Error containing detailed contextual information func (p *Parser) createDetailedError(err error) error { if parseErr, ok := err.(*ParseError); ok { parseErr.Context = FormatErrorContext(p.input, parseErr.Position, 20) @@ -277,9 +277,9 @@ func (p *Parser) createDetailedError(err error) error { return err } -// createCombinedError 创建组合错误信息 -// 将多个解析错误合并为一个统一的错误消息,便于用户理解所有问题 -// 返回: 包含所有错误信息的组合错误 +// createCombinedError creates a combined error message +// Merging multiple parsing errors into a unified error message makes it easier for users to understand all issues +// Return: Combination error containing all error messages func (p *Parser) createCombinedError() error { errors := p.errorRecovery.GetErrors() if len(errors) == 1 { @@ -294,15 +294,15 @@ func (p *Parser) createCombinedError() error { return fmt.Errorf("%s", builder.String()) } -// parseSelect 解析 SELECT 子句,包括字段列表、DISTINCT 关键字和别名 -// 支持 SELECT * 语法,并提供字段数量限制防止无限循环 -// 参数: stmt - 要填充的 SelectStatement 结构体 -// 返回: 解析过程中遇到的错误,如果成功则返回 nil +// parseSelect parses the SELECT clause, including the field list, DISTINCT keywords, and aliases +// Supports SELECT * syntax and provides field limit to prevent infinite loops +// Parameter: stmt - The SelectStatement structure to be filled +// Return: Error encountered during parsing; if successful, returns nil func (p *Parser) parseSelect(stmt *SelectStatement) error { // Validate if first token is SELECT firstToken := p.lexer.NextToken() if firstToken.Type != TokenSELECT { - // 直接返回语法错误 + // Directly returns grammar errors return CreateSyntaxError( fmt.Sprintf("Expected SELECT, got %s", firstToken.Value), firstToken.Pos, @@ -314,27 +314,27 @@ func (p *Parser) parseSelect(stmt *SelectStatement) error { if currentToken.Type == TokenDISTINCT { stmt.Distinct = true - currentToken = p.lexer.NextToken() // 消费 DISTINCT,移动到下一个 token + currentToken = p.lexer.NextToken() // Spend DISTINCT to move to the next token } - // 检查是否是SELECT *查询(词法器把 * 归为 TokenAsterisk,非 TokenIdent) + // Check if it is a SELECT * query (the lexist classifies * as TokenAsterisk, not TokenIdent) if currentToken.Type == TokenAsterisk { stmt.SelectAll = true - // 添加一个特殊的字段标记SELECT * + // Add a special field tag SELECT * stmt.Fields = append(stmt.Fields, Field{Expression: "*"}) - // 消费*token并检查下一个token + // Spend *token and check the next token currentToken = p.lexer.NextToken() - // 如果下一个token是FROM或EOF,则完成SELECT *解析 + // If the next token is FROM or EOF, SELECT * parse is completed if currentToken.Type == TokenFROM || currentToken.Type == TokenEOF { return nil } - // 如果不是FROM/EOF,继续正常的字段解析流程 + // If it is not FROM/EOF, continue with the normal field parsing process } - // 设置最大字段数量限制,防止无限循环 + // Set a maximum field limit to prevent endless loops fieldCount := 0 for { @@ -345,67 +345,67 @@ func (p *Parser) parseSelect(stmt *SelectStatement) error { } var expr strings.Builder - parenthesesLevel := 0 // 跟踪括号嵌套层级 + parenthesesLevel := 0 // Track parentheses nested hierarchies - // 设置最大表达式长度,防止无限循环 + // Set the maximum expression length to prevent infinite loops maxExprParts := 100 exprPartCount := 0 for { exprPartCount++ - // 安全检查:防止无限循环 + // Safety check: prevents endless loops if exprPartCount > maxExprParts { return errors.New("select field expression parsing exceeded maximum length, possible syntax error") } - // 跟踪括号层级 + // Track the parenthesis level if currentToken.Type == TokenLParen { parenthesesLevel++ } else if currentToken.Type == TokenRParen { parenthesesLevel-- } - // 只有在括号层级为0时,逗号才被视为字段分隔符 + // Commas are only considered field separators when the parenthesis level is 0 if parenthesesLevel == 0 && (currentToken.Type == TokenFROM || currentToken.Type == TokenComma || currentToken.Type == TokenAS || currentToken.Type == TokenEOF || currentToken.Type == TokenOVER) { break } - // 如果不是第一个token,添加空格分隔符 - // 但要注意特殊情况:某些token之间不应该加空格 + // If not the first token, add a space separator + // But note special cases: certain tokens should not be spaced between tokens if expr.Len() > 0 { shouldAddSpace := true - // 获取前一个token的信息 + // Retrieve information about the previous token exprStr := expr.String() lastChar := exprStr[len(exprStr)-1:] - // 以下情况不添加空格: - // 1. 函数名和左括号之间 - // 2. 标识符和数字之间(如 x1, y1) - // 3. 数字和标识符之间 - // 4. 左括号之后 - // 5. 右括号之前 - // 6. 数组索引相关:[ 前,[ 后,] 前 - // 7. 点号前后 + // No spaces are added in the following cases: + // 1. Between the function name and the left parenthesis + // 2. Between identifiers and numbers (e.g., x1, y1) + // 3. Between numbers and identifiers + // 4. After the left parentheses + // 5. Before the right parentheses + // 6. Array index correlation: [ before, [ after ] + // 7. Before and after the dot mark if (currentToken.Type == TokenLParen || currentToken.Type == TokenLBracket) && lastChar != " " && lastChar != "(" && lastChar != "[" { - // 函数名/数组名和左括号/左中括号之间不加空格 + // No spaces are added between function names/array names and left parentheses/left square brackets shouldAddSpace = false } else if lastChar == "(" || lastChar == "[" || currentToken.Type == TokenRParen || currentToken.Type == TokenRBracket { - // 左括号/左中括号之后或右括号/右中括号之前不加空格 + // No spaces are added after left or right square brackets shouldAddSpace = false } else if currentToken.Type == TokenDot || lastChar == "." { - // 点号前后不加空格 + // No spaces are added before or after the dot sign shouldAddSpace = false } else if len(exprStr) > 0 && currentToken.Type == TokenNumber { - // 检查前一个字符是否是字母(标识符的一部分),且前面没有空格 - // 这主要处理 x1, y1 这类标识符,但排除 THEN 1, ELSE 0 这类情况 + // Check whether the previous character is a letter (part of the identifier) and that there are no spaces before it + // This mainly handles identifiers like x1 and y1, but excludes cases like THEN 1 and ELSE 0 if ((lastChar[0] >= 'a' && lastChar[0] <= 'z') || (lastChar[0] >= 'A' && lastChar[0] <= 'Z') || lastChar[0] == '_') && !strings.HasSuffix(exprStr, " ") { - // 进一步检查:如果前面是SQL关键字,则应该加空格 + // Further check: If the preceding word is an SQL keyword, you should add a space words := strings.Fields(exprStr) if len(words) > 0 { lastWord := strings.ToUpper(words[len(words)-1]) - // 如果是关键字,应该加空格 + // If it's a keyword, you should add a space if isKeyword(lastWord) { shouldAddSpace = true } else { @@ -416,7 +416,7 @@ func (p *Parser) parseSelect(stmt *SelectStatement) error { } } } else if len(exprStr) > 0 && (currentToken.Type == TokenIdent || currentToken.Type == TokenQuotedIdent) { - // 检查前一个字符是否是数字,且前面没有空格 + // Check whether the previous character is a number and that there are no spaces in front if (lastChar[0] >= '0' && lastChar[0] <= '9') && !strings.HasSuffix(exprStr, " ") { shouldAddSpace = false } @@ -432,9 +432,9 @@ func (p *Parser) parseSelect(stmt *SelectStatement) error { field := Field{Expression: strings.TrimSpace(expr.String())} - // 解析可选的 OVER 子句(分析函数。OVER 在断点条件中被识别, - // 此处 currentToken == TokenOVER;parseOverClause 消费 OVER(...),返回后 ) - // 已读出,再 NextToken 取后续 token(AS/FROM/Comma/EOF)。 + // Parse the optional OVER clause (parsing function). OVER is recognized under breakpoint conditions, + // Here: currentToken == TokenOVER; parseOverClause consumes OVER(...), returns) + // Already read, NextToken retrieves subsequent tokens (AS/FROM/Comma/EOF). if currentToken.Type == TokenOVER { over, err := p.parseOverClause() if err != nil { @@ -444,15 +444,15 @@ func (p *Parser) parseSelect(stmt *SelectStatement) error { currentToken = p.lexer.NextToken() } - // 处理别名 + // Handling aliases if currentToken.Type == TokenAS { field.Alias = p.lexer.NextToken().Value currentToken = p.lexer.NextToken() } - // 如果表达式为空,跳过这个字段 + // If the expression is empty, skip this field if field.Expression != "" { - // 验证表达式中的函数 + // Verify the function in the expression validator := NewFunctionValidator(p.errorRecovery) pos, _, _ := p.lexer.GetPosition() validator.ValidateExpression(field.Expression, pos-len(field.Expression)) @@ -465,14 +465,14 @@ func (p *Parser) parseSelect(stmt *SelectStatement) error { } if currentToken.Type != TokenComma { - // 如果不是逗号,那么应该是语法错误 + // If it's not a comma, then it should be a grammatical error return fmt.Errorf("unexpected token %v, expected comma or FROM", currentToken.Value) } currentToken = p.lexer.NextToken() } - // 确保至少有一个字段 + // Make sure to have at least one field if len(stmt.Fields) == 0 { return errors.New("no fields specified in SELECT clause") } @@ -482,9 +482,9 @@ func (p *Parser) parseSelect(stmt *SelectStatement) error { func (p *Parser) parseWhere(stmt *SelectStatement) error { var conditions []string - current := p.lexer.NextToken() // 获取下一个token + current := p.lexer.NextToken() // Obtain the next token if current.Type != TokenWHERE { - // 如果不是WHERE,回退token位置 + // If not WHERE (not HERE), revert the token position return nil } @@ -494,7 +494,7 @@ func (p *Parser) parseWhere(stmt *SelectStatement) error { for { iterations++ - // 安全检查:防止无限循环 + // Safety check: prevents endless loops if iterations > maxIterations { return errors.New("WHERE clause parsing exceeded maximum iterations, possible syntax error") } @@ -540,8 +540,8 @@ func (p *Parser) parseWhere(stmt *SelectStatement) error { } } - // Validate functions in WHERE condition. 分析函数调用(含 OVER)先替换为占位符, - // 避免 OVER 被误判为未知函数;stmt.Condition 保留原文,由 ToStreamConfig 提取。 + // Validate functions in WHERE condition. Parser function calls (including OVER) are first replaced with placeholders, + // Avoid OVER being mistakenly identified as an unknown function; stmt.Condition retains the original text and is extracted by ToStreamConfig. whereCondition := strings.Join(conditions, " ") if whereCondition != "" { validated, _, _ := extractWhereAnalyticCalls(whereCondition) @@ -555,7 +555,7 @@ func (p *Parser) parseWhere(stmt *SelectStatement) error { } func (p *Parser) parseWindowFunction(stmt *SelectStatement, winType string) error { - nextTok := p.lexer.NextToken() // 读取下一个 token,应该是 '(' + nextTok := p.lexer.NextToken() // Read the next token, which should be '(' if nextTok.Type != TokenLParen { return fmt.Errorf("expected '(' after window function %s, got %s (type: %v)", winType, nextTok.Value, nextTok.Type) } @@ -664,10 +664,10 @@ func (p *Parser) parseGlobalWindow(stmt *SelectStatement) error { return nil } -// parseOverClause 解析分析函数的 OVER 子句:OVER ([PARTITION BY ...] [WHEN ...])。 -// 仅支持 PARTITION BY 和 WHEN,ORDER BY / ROWS / BETWEEN 一律报错。 -// 约定:调用时 currentToken == TokenOVER(已读出),lexer 待读为 '(';返回时 -// OVER(...) 已全部消费,'(' 内的 ')' 是最后读出的 token,调用者需 NextToken 取后续。 +// parseOverClause parsing the OVER clause of the analysis function: OVER ([PARTITION BY...] [WHEN...]). +// Only PARTITION BY and WHEN are supported; ORDER BY / ROWS / BETWEEN will all show errors. +// Convention: When called, currentToken == TokenOVER (read), lexer is to be read as '('; On the way back +// OVER(...) has been fully consumed, and the ')' in '(' is the last token read; the caller needs NextToken to retrieve the following. func (p *Parser) parseOverClause() (*types.OverSpec, error) { lp := p.lexer.NextToken() if lp.Type != TokenLParen { @@ -695,7 +695,7 @@ func (p *Parser) parseOverClause() (*types.OverSpec, error) { } } -// parseOverPartitionBy 解析 PARTITION BY [, ...]。PARTITION 已读出。 +// parseOverPartitionBy parses PARTITION BY [, ...]. PARTITION has been read. func (p *Parser) parseOverPartitionBy(spec *types.OverSpec) error { by := p.lexer.NextToken() if by.Type != TokenBY { @@ -706,7 +706,7 @@ func (p *Parser) parseOverPartitionBy(spec *types.OverSpec) error { if id.Type != TokenIdent && id.Type != TokenQuotedIdent { return fmt.Errorf("expected partition field after PARTITION BY, got %q", id.Value) } - // 去掉反引号 + // Remove the quotation marks name := id.Value if len(name) >= 2 && name[0] == '`' && name[len(name)-1] == '`' { name = name[1 : len(name)-1] @@ -717,14 +717,14 @@ func (p *Parser) parseOverPartitionBy(spec *types.OverSpec) error { if sep.Type == TokenComma { continue } - p.lexer.restore(snap) // 回退(WHEN 或 ')'),交给上层循环 + p.lexer.restore(snap) // Fallback (WHEN or ')'), and pass it to the upper loop return nil } } -// parseOverWhen 解析 WHEN ,收集到 ) 或 PARTITION 为止。WHEN 已读出。 -// 跟踪括号深度:WHEN 谓词里的函数调用(如 had_changed(true, status))的括号要计入, -// 仅在深度归零时 ')' 才是 OVER 子句结束。 +// parseOverWhen parses WHEN until) or PARTITION. WHEN has been read. +// Tracking parenthesis depth: WHEN function calls in the predicate (such as had_changed(true, status)), parentheses must be counted, +// Only when the depth is zeroed ')' does the OVER clause end. func (p *Parser) parseOverWhen() (string, error) { var parts []string depth := 0 @@ -768,7 +768,7 @@ func convertValue(s string) any { if f, err := strconv.ParseFloat(s, 64); err == nil { return f } - // 处理引号包裹的字符串 + // Handle strings wrapped in quotes if strings.HasPrefix(s, "'") && strings.HasSuffix(s, "'") { return strings.Trim(s, "'") } @@ -821,7 +821,7 @@ func isClauseBoundaryIdent(value string) bool { switch strings.ToUpper(value) { case "JOIN", "INNER", "LEFT", "RIGHT", "FULL", "CROSS", "ON", "WHERE", "GROUP", "HAVING", "ORDER", "LIMIT", "WITH", - "MATCH_RECOGNIZE": // 子句起点(词法器把 MATCH_RECOGNIZE 读成单标识符),不得当源别名消费 + "MATCH_RECOGNIZE": // The clause starting point (the lexical tool reads MATCH_RECOGNIZE as a single identifier) cannot be consumed as a source alias return true } return false @@ -985,22 +985,22 @@ func (p *Parser) parseGroupBy(stmt *SelectStatement) error { hasGroupBy := false if tok.Type == TokenGROUP { hasGroupBy = true - p.lexer.NextToken() // 跳过BY + p.lexer.NextToken() // Skip BY } - // 如果没有GROUP BY子句且没有窗口函数,直接返回 + // If there is no GROUP BY clause and no window function, it returns directly if !hasGroupBy && !hasWindowFunction { return nil } - // 设置最大次数限制,防止无限循环 + // Set a maximum limit to prevent infinite loops maxIterations := 100 iterations := 0 - var limitToken *Token // 保存LIMIT token以便后续处理 + var limitToken *Token // Save LIMIT tokens for future processing - // 累积分组项:跟踪括号深度,把函数表达式(如 upper(device))作为整体一项, - // 顶层逗号分隔。collapseSpacesOutsideQuotes 归一化(parser 读出的多 token 带空格)。 + // Cumulative subgroup terms: track the depth of parentheses and treat function expressions (such as upper(device)) as the overall term, + // The top layer is separated by commas. collapseSpacesOutsideQuotes normalization (multiple tokens read by parser with spaces). var currentItem strings.Builder parenLevel := 0 flushItem := func() { @@ -1012,7 +1012,7 @@ func (p *Parser) parseGroupBy(stmt *SelectStatement) error { for { iterations++ - // 安全检查:防止无限循环 + // Safety check: prevents endless loops if iterations > maxIterations { return errors.New("group by clause parsing exceeded maximum iterations, possible syntax error") } @@ -1020,7 +1020,7 @@ func (p *Parser) parseGroupBy(stmt *SelectStatement) error { tok := p.lexer.NextToken() if tok.Type == TokenWITH || tok.Type == TokenOrder || tok.Type == TokenEOF || tok.Type == TokenHAVING || tok.Type == TokenLIMIT { - // 如果是LIMIT token,保存它以便parseLimit处理 + // If it is a LIMIT token, save it for parseLimit to handle if tok.Type == TokenLIMIT { limitToken = &tok } @@ -1031,7 +1031,7 @@ func (p *Parser) parseGroupBy(stmt *SelectStatement) error { flushItem() continue } - // 顶层(括号外)的窗口/全局窗/OVER 子句:先收尾当前项再处理。 + // Top-level (outside parentheses) window/global window/OVER clause: wrap the current item before processing. if parenLevel == 0 { if tok.Type == TokenGlobal { flushItem() @@ -1049,8 +1049,8 @@ func (p *Parser) parseGroupBy(stmt *SelectStatement) error { continue } if tok.Type == TokenOVER { - // GROUP BY window 的 OVER(...) 子句(仅 WHEN 输入门控)。校验在 ToStreamConfig - // 做(parseGroupBy 的返回错误会被 errorRecovery 当作可恢复错误吞掉)。 + // GROUP BY window's OVER(...) clause (only WHEN input gating). Verification is done in ToStreamConfig + // (parseGroupBy return errors will be swallowed as recoverable errors by errorRecovery). flushItem() over, err := p.parseOverClause() if err != nil { @@ -1066,7 +1066,7 @@ func (p *Parser) parseGroupBy(stmt *SelectStatement) error { continue } } - // 跟踪括号深度(函数调用参数),把 token 累积进当前分组项。 + // Track parenthesis depth (function call parameters) to accumulate tokens into the current group. if tok.Type == TokenLParen { parenLevel++ } else if tok.Type == TokenRParen { @@ -1078,7 +1078,7 @@ func (p *Parser) parseGroupBy(stmt *SelectStatement) error { currentItem.WriteString(tok.Value) } - // 如果遇到了LIMIT token,直接在这里处理 + // If you encounter a LIMIT token, handle it directly here if limitToken != nil { return p.handleLimitToken(stmt, *limitToken) } @@ -1086,21 +1086,21 @@ func (p *Parser) parseGroupBy(stmt *SelectStatement) error { } func (p *Parser) parseWith(stmt *SelectStatement) error { - // 查看当前 token,如果不是 WITH,则返回 + // Check the current token; if it is not WITH, it returns tok := p.lexer.lookupIdent(p.lexer.readPreviousIdentifier()) if tok.Type != TokenWITH { - return nil // 没有 WITH 子句,不是错误 + return nil // No WITH clause, not an error } - p.lexer.NextToken() // 跳过( + p.lexer.NextToken() // Skip ( - // 设置最大次数限制,防止无限循环 + // Set a maximum limit to prevent infinite loops maxIterations := 100 iterations := 0 for p.lexer.peekChar() != ')' { iterations++ - // 安全检查:防止无限循环 + // Safety check: prevents endless loops if iterations > maxIterations { return errors.New("WITH clause parsing exceeded maximum iterations, possible syntax error") } @@ -1261,12 +1261,12 @@ func (p *Parser) parseWith(stmt *SelectStatement) error { return nil } -// handleLimitToken 处理在parseGroupBy中遇到的LIMIT token +// handleLimitToken handles LIMIT tokens encountered in parseGroupBy func (p *Parser) handleLimitToken(stmt *SelectStatement, limitToken Token) error { - // 获取下一个token,应该是一个数字 + // The next token should be a number tok := p.lexer.NextToken() if tok.Type == TokenNumber { - // 将数字字符串转换为整数 + // Convert numeric strings to integers limit, err := strconv.Atoi(tok.Value) if err != nil { parseErr := CreateSyntaxError( @@ -1296,7 +1296,7 @@ func (p *Parser) handleLimitToken(stmt *SelectStatement, limitToken Token) error } stmt.Limit = limit } else if tok.Type == TokenMinus { - // 处理负数情况:"-5" + // Handling negative numbers: "-5" nextTok := p.lexer.NextToken() if nextTok.Type == TokenNumber { parseErr := CreateSyntaxError( @@ -1321,7 +1321,7 @@ func (p *Parser) handleLimitToken(stmt *SelectStatement, limitToken Token) error return parseErr } } else { - // 处理非数字情况:如 "abc" + // Handling non-numeric cases: such as "abc" parseErr := CreateMissingTokenError("number", tok.Pos) parseErr.Message = "LIMIT must be followed by an integer" parseErr.Context = "LIMIT clause" @@ -1335,14 +1335,14 @@ func (p *Parser) handleLimitToken(stmt *SelectStatement, limitToken Token) error return nil } -// parseLimit 解析LIMIT子句 +// parseLimit parses the LIMIT clause func (p *Parser) parseLimit(stmt *SelectStatement) error { - // 如果LIMIT已经被设置(可能在parseGroupBy中已处理),则跳过 + // If LIMIT has already been set (possibly processed in parseGroupBy), skip it if stmt.Limit > 0 { return nil } - // 在 token 流中查找真正的 LIMIT 关键字,避免误匹配标识符/字符串字面量中的子串 + // Find the true LIMIT keyword in the token stream to avoid mismatching substrings in identifiers/string literals limitLexer := NewLexer(p.input) limitLexer.SetErrorRecovery(NewErrorRecovery(nil)) limitIndex := -1 @@ -1360,8 +1360,8 @@ func (p *Parser) parseLimit(stmt *SelectStatement) error { return nil } - // 找到LIMIT后面的内容 - afterLimit := strings.TrimSpace(p.input[limitIndex+5:]) // 跳过"LIMIT" + // Find the content after LIMIT + afterLimit := strings.TrimSpace(p.input[limitIndex+5:]) // Skip "LIMIT" if afterLimit == "" { parseErr := CreateMissingTokenError("number", limitIndex+5) parseErr.Message = "LIMIT must be followed by an integer" @@ -1374,7 +1374,7 @@ func (p *Parser) parseLimit(stmt *SelectStatement) error { return parseErr } - // 分割出第一个单词(应该是数字) + // Break down the first word (it should be a number) parts := strings.Fields(afterLimit) if len(parts) == 0 { parseErr := CreateMissingTokenError("number", limitIndex+5) @@ -1390,7 +1390,7 @@ func (p *Parser) parseLimit(stmt *SelectStatement) error { limitValue := parts[0] - // 处理负数情况 + // Handle negative numbers if strings.HasPrefix(limitValue, "-") { parseErr := CreateMissingTokenError("number", limitIndex+6) parseErr.Message = "LIMIT must be followed by an integer" @@ -1400,7 +1400,7 @@ func (p *Parser) parseLimit(stmt *SelectStatement) error { return parseErr } - // 尝试转换为整数 + // Try converting to an integer limit, err := strconv.Atoi(limitValue) if err != nil { parseErr := CreateMissingTokenError("number", limitIndex+6) @@ -1427,11 +1427,11 @@ func (p *Parser) parseLimit(stmt *SelectStatement) error { return nil } -// parseOrderBy 解析 ORDER BY 子句。用独立 lexer 扫描真正的 TokenOrder, -// 避免误匹配标识符/字符串字面量中的 "ORDER" 子串(与 parseLimit 同样的稳健做法)。 -// v0.5:每个排序键为结果列名(标识符,可含点路径),后接可选 ASC/DESC,逗号分隔。 +// parseOrderBy parses the ORDER BY clause. Scan the true TokenOrder with an independent lexer, +// Avoid mismatching the "ORDER" substring in identifiers/string literals (the same robust approach as parseLimit). +// v0.5: Each sort key is the result column name (identifier, can include dot paths), followed by optional ASC/DESC, separated by commas. func (p *Parser) parseOrderBy(stmt *SelectStatement) error { - // 用独立 lexer 定位真正的 ORDER 关键字位置 + // Use independent lexer to locate the true ORDER keyword orderLexer := NewLexer(p.input) orderLexer.SetErrorRecovery(NewErrorRecovery(nil)) orderPos := -1 @@ -1446,15 +1446,15 @@ func (p *Parser) parseOrderBy(stmt *SelectStatement) error { } } if orderPos == -1 { - return nil // 无 ORDER BY 子句 + return nil // No ORDER BY clause } - // 从 ORDER 之后重新 lex,解析 BY 及字段列表 + // Re-lex from ORDER to parse BY and the field list fieldLexer := NewLexer(p.input[orderPos+len("ORDER"):]) fieldLexer.SetErrorRecovery(NewErrorRecovery(nil)) if tok := fieldLexer.NextToken(); tok.Type != TokenBY { - // ORDER 后不是 BY,不当作 ORDER BY(例如列名含 ORDER 子串已被上面的 token 扫描排除) + // If the ORDER is not BY, it should not be treated as an ORDER BY (for example, column names containing ORDER substrings have been scanned and excluded by the token above). return nil } @@ -1476,14 +1476,14 @@ func (p *Parser) parseOrderBy(stmt *SelectStatement) error { advance = true break } - // ASC/DESC 作为方向关键字(它们没有独立 token,按标识符值识别) + // ASC/DESC as direction keywords (they do not have independent tokens and are identified by identifier values) if tok.Type == TokenIdent { upper := strings.ToUpper(tok.Value) if upper == "ASC" || upper == "DESC" { if upper == "DESC" { dir = types.SortDesc } - // 方向已消费,其后应为逗号或子句结束 + // The direction has been consumed, followed by a comma or clause sep := fieldLexer.NextToken() if sep.Type == TokenComma { advance = true @@ -1493,7 +1493,7 @@ func (p *Parser) parseOrderBy(stmt *SelectStatement) error { break } } - // 追加 token 值(不加分隔符,使 a.b / backtick 字段能正确重建) + // Add token value (no separator, so the a.b / backtick fields can be correctly reconstructed) exprBuilder.WriteString(tok.Value) } @@ -1509,22 +1509,22 @@ func (p *Parser) parseOrderBy(stmt *SelectStatement) error { return nil } -// parseHaving 解析HAVING子句 +// parseHaving parses the HAVING clause func (p *Parser) parseHaving(stmt *SelectStatement) error { - // 查看当前token + // View the current token tok := p.lexer.lookupIdent(p.lexer.readPreviousIdentifier()) if tok.Type != TokenHAVING { - return nil // 没有 HAVING 子句,不是错误 + return nil // No HAVING clause, not an error } - // 设置最大次数限制,防止无限循环 + // Set a maximum limit to prevent infinite loops maxIterations := 100 iterations := 0 var conditions []string for { iterations++ - // 安全检查:防止无限循环 + // Safety check: prevents endless loops if iterations > maxIterations { return errors.New("HAVING clause parsing exceeded maximum iterations, possible syntax error") } @@ -1579,7 +1579,7 @@ func (p *Parser) parseHaving(stmt *SelectStatement) error { return nil } -// Parse 是包级别的Parse函数,用于解析SQL字符串并返回配置和条件 +// Parse is a packet-level Parse function used to parse SQL strings and return configurations and conditions func Parse(sql string) (*types.Config, string, error) { parser := NewParser(sql) stmt, err := parser.Parse() @@ -1592,11 +1592,11 @@ func Parse(sql string) (*types.Config, string, error) { return nil, "", err } - // Reject malformed GROUP BY. 用原始 stmt.GroupBy(extractGroupFields 过滤前), - // 否则 isAggregationFunction 的"含括号保守判聚合"兜底会把拼错的窗口函数 - // (如 InvalidWindow('5s'))当聚合丢掉,使 config.GroupFields 为空、校验落空。 - // 合法分组项:裸列名,或顶层为已注册标量函数的表达式(如 upper(device))。 - // 引号 artifact 或未注册函数 → 视为拼错的窗口函数泄漏,拒绝。 + // Reject malformed GROUP BY. Using the original stmt.GroupBy(extractGroupFields before filtering), + // Otherwise, the "Conservative Aggregation with Parentheses" in isAggregationFunction will catch the misspelled window function + // (e.g., InvalidWindow('5s')) When the aggregation is discarded, config.GroupFields is null, validation is missing. + // Valid grouped items: bare column names, or expressions with registered scalar functions at the top (e.g., upper(device)). + // Quotation marks like artifact or unregistered function → are considered misspelled window function leaks, rejected. for _, g := range stmt.GroupBy { if strings.ContainsAny(g, "'\"") { return nil, "", fmt.Errorf("invalid GROUP BY field %q: unknown window function or unsupported expression", g) diff --git a/rsql/parser_match.go b/rsql/parser_match.go index 440fc9c..54348a0 100644 --- a/rsql/parser_match.go +++ b/rsql/parser_match.go @@ -10,7 +10,7 @@ import ( "github.com/rulego/streamsql/types" ) -// isMRClauseKeyword 报告 ident 是否是 MATCH_RECOGNIZE 子句起始关键字(用于表达式收集终止)。 +// isMRClauseKeyword reports whether ident is the starting key for MATCH_RECOGNIZE clauses (used to terminate expression collection). func isMRClauseKeyword(v string) bool { switch strings.ToUpper(v) { case "PARTITION", "ORDER", "MEASURES", "ONE", "ALL", "AFTER", @@ -20,7 +20,7 @@ func isMRClauseKeyword(v string) bool { return false } -// peekToken 不消费地预读一个 token(基于 lexer save/restore)。 +// peekToken prefetchs a token without consumption (based on lexer save/restore). func (p *Parser) peekToken() Token { snap := p.lexer.save() t := p.lexer.NextToken() @@ -28,11 +28,11 @@ func (p *Parser) peekToken() Token { return t } -// parseMatchRecognize 解析 FROM 之后的可选 MATCH_RECOGNIZE(...) 子句。 -// 识别完整语法;未实现子句给明确错误。约定:进入时 lexer 在 MATCH 之前;非 MATCH 则不动。 -// parseMatchRecognize 解析 FROM 之后的可选 MATCH_RECOGNIZE(...) 子句。 -// 识别完整语法;未实现子句给明确错误。约定:进入时 lexer 在 MATCH_RECOGNIZE 之前;非该关键字则不动。 -// 注:词法器把 MATCH_RECOGNIZE 读成单个标识符 token(下划线连接)。 +// parseMatchRecognize parses the optional MATCH_RECOGNIZE(...) clause after FROM. +// Recognize complete grammar; Unrealized clauses give clear errors. Agreement: Lexer enters before MATCH; If not MATCH, it won't move. +// parseMatchRecognize parses the optional MATCH_RECOGNIZE(...) clause after FROM. +// Recognize complete grammar; Unrealized clauses give clear errors. Agreement: Lexer enters before MATCH_RECOGNIZE; Do not move any keywords that are not the same keywords. +// Note: The lexicon reads MATCH_RECOGNIZE as a single identifier token (underline conjunction). func (p *Parser) parseMatchRecognize(stmt *SelectStatement) error { snap := p.lexer.save() t := p.lexer.NextToken() @@ -52,7 +52,7 @@ func (p *Parser) parseMatchRecognize(stmt *SelectStatement) error { for { t := p.lexer.NextToken() if t.Type == TokenRParen { - break // MATCH_RECOGNIZE 结束 + break // MATCH_RECOGNIZE End } kw := strings.ToUpper(t.Value) switch kw { @@ -81,13 +81,13 @@ func (p *Parser) parseMatchRecognize(stmt *SelectStatement) error { } spec.Measures = ms case "ONE": - // SQL 标准:ONE ROW PER MATCH + // SQL standard: ONE ROW PER MATCH if err := p.expectRowPerMatch("ROW"); err != nil { return err } spec.RowsPerMatch = types.RowsPerMatchOne case "ALL": - // SQL 标准:ALL ROWS PER MATCH(复数) + // SQL standard: ALL ROWS PER MATCH (PLURAL) if err := p.expectRowPerMatch("ROWS"); err != nil { return err } @@ -125,12 +125,12 @@ func (p *Parser) parseMatchRecognize(stmt *SelectStatement) error { } } - // Pattern/OrderBy 必需性在 ToStreamConfig 校验(其错误不被解析恢复吞掉)。 + // Pattern/OrderBy is required in ToStreamConfig validation (its errors are not swallowed by parsing recovery). stmt.MatchRecognize = spec return nil } -// expectKeyword 消费一个值匹配(大小写无关)的 ident token。 +// expectKeyword consumes an ident token that matches a value (case-insensitive). func (p *Parser) expectKeyword(want string) error { t := p.lexer.NextToken() if !strings.EqualFold(t.Value, want) { @@ -139,7 +139,7 @@ func (p *Parser) expectKeyword(want string) error { return nil } -// readIdentList 读取逗号分隔的标识符列表(去反引号),到非逗号终止符止(不消费终止符)。 +// readIdentList reads the comma-separated identifier list (without backquotes), and stops at the non-comma terminator (no terminator is consumed). func (p *Parser) readIdentList() ([]string, error) { var fields []string for { @@ -164,7 +164,7 @@ func stripBackticks(s string) string { return s } -// readMROrderBy 读取 ORDER BY 字段[ ASC|DESC], ... 到非逗号终止符止。 +// readMROrderBy reads the ORDER BY field[ASC| DESC],... Ends with a non-comma termination. func (p *Parser) readMROrderBy() ([]types.OrderByField, error) { var obs []types.OrderByField for { @@ -173,13 +173,13 @@ func (p *Parser) readMROrderBy() ([]types.OrderByField, error) { return nil, fmt.Errorf("expected ORDER BY field, got %q", t.Value) } f := types.OrderByField{Expression: stripBackticks(t.Value), Direction: types.SortAsc} - // 可选 ASC/DESC + // Optional ASC/DESC snap := p.lexer.save() dir := p.lexer.NextToken() if strings.EqualFold(dir.Value, "DESC") { f.Direction = types.SortDesc } else if strings.EqualFold(dir.Value, "ASC") { - // 默认 + // Default } else { p.lexer.restore(snap) } @@ -193,7 +193,7 @@ func (p *Parser) readMROrderBy() ([]types.OrderByField, error) { } } -// expectRowPerMatch 消费 " PER MATCH"(rowKeyword 为 ROW 或 ROWS)。 +// expectRowPerMatch Consumption " PER MATCH" (rowKeyword is ROW or ROWS). func (p *Parser) expectRowPerMatch(rowKeyword string) error { for _, want := range []string{rowKeyword, "PER", "MATCH"} { if err := p.expectKeyword(want); err != nil { @@ -203,7 +203,7 @@ func (p *Parser) expectRowPerMatch(rowKeyword string) error { return nil } -// readMRMeasures 读取 MEASURES AS , ... 到非逗号终止符止。 +// readMRMeasures Read MEASURES AS,... Ends with a non-comma termination. func (p *Parser) readMRMeasures() ([]types.Measure, error) { var ms []types.Measure for { @@ -228,7 +228,7 @@ func (p *Parser) readMRMeasures() ([]types.Measure, error) { } } -// readMRDefines 读取 DEFINE AS , ... 到非逗号终止符止。 +// readMRDefines Read DEFINE AS,... Ends with a non-comma termination. func (p *Parser) readMRDefines() ([]types.MatchDefine, error) { var ds []types.MatchDefine for { @@ -253,7 +253,7 @@ func (p *Parser) readMRDefines() ([]types.MatchDefine, error) { } } -// readMRSubsets 读取 SUBSET = (sym, ...), ... 到非逗号终止符止。 +// readMRSubsets reads SUBSET = (sym,...),... Ends with a non-comma termination. func (p *Parser) readMRSubsets() ([]types.MatchSubset, error) { var ss []types.MatchSubset for { @@ -284,7 +284,7 @@ func (p *Parser) readMRSubsets() ([]types.MatchSubset, error) { } } -// readMRAfterMatchSkip 解析 AFTER MATCH SKIP (...)。 +// readMRAfterMatchSkip parses AFTER MATCH SKIP (...). func (p *Parser) readMRAfterMatchSkip(spec *types.MatchRecognizeSpec) error { if err := p.expectKeyword("MATCH"); err != nil { return err @@ -325,7 +325,7 @@ func (p *Parser) readMRAfterMatchSkip(spec *types.MatchRecognizeSpec) error { spec.Skip = types.SkipToLast spec.SkipSymbol = sym default: - // TO (无 FIRST/LAST) + // TO (No FIRST/LAST) spec.Skip = types.SkipToVariable spec.SkipSymbol = n.Value } @@ -343,12 +343,12 @@ func (p *Parser) readSymbol() (string, error) { return stripBackticks(t.Value), nil } -// isMRSymbolToken 报告 token 是否可作为模式变量符号(标识符/反引号标识符/被归为关键字的标识符词)。 +// isMRSymbolToken reports whether tokens can be used as pattern variable symbols (identifiers/backquotes/identifiers classified as keywords). func isMRSymbolToken(t Token) bool { return t.Type == TokenQuotedIdent || isMRIdentLike(t) } -// parseMRDuration 解析 WITHIN 时长:'5s' / 5 SECONDS / 100 MS 等。 +// parseMRDuration WITHIN duration: '5s' / 5 SECONDS / 100 MS, etc. func (p *Parser) parseMRDuration() (time.Duration, error) { t := p.lexer.NextToken() if t.Type == TokenString { @@ -392,14 +392,14 @@ func durationUnit(unit string, n float64) (time.Duration, bool) { return 0, false } -// readMRUntilAS 收集表达式到顶层 AS(不消费 AS)。 +// readMRUntilAS collects expressions to the top-level AS (no AS is consumed). func (p *Parser) readMRUntilAS() (string, error) { var parts []string depth := 0 for i := 0; i < 1000; i++ { snap := p.lexer.save() t := p.lexer.NextToken() - // AS 被词法器归为 TokenAS(关键字),非 TokenIdent;顶层 AS 终止表达式收集。 + // AS is classified as TokenAS (keyword) by the lexical editor, not as TokenIdent; Top-level AS termination expression collection. if depth == 0 && t.Type == TokenAS { p.lexer.restore(snap) return strings.Join(parts, " "), nil @@ -415,7 +415,7 @@ func (p *Parser) readMRUntilAS() (string, error) { return "", errors.New("MEASURES expression too long (missing AS)") } -// readMRExpr 收集表达式到顶层 ',' 或 ')' 或子句关键字(不消费终止符)。 +// readMRExpr collects expressions to the top-level ',' or ')' or clause keywords (without consuming terminators). func (p *Parser) readMRExpr() (string, error) { var parts []string depth := 0 diff --git a/rsql/parser_match_pattern.go b/rsql/parser_match_pattern.go index bc87ba5..36dd64b 100644 --- a/rsql/parser_match_pattern.go +++ b/rsql/parser_match_pattern.go @@ -8,7 +8,7 @@ import ( "github.com/rulego/streamsql/types" ) -// parseMRPatternBody 解析 PATTERN ( <正则> )。约定:调用时 lexer 在 '(' 之前。 +// parseMRPatternBody parses PATTERN (). The lexer must be positioned before '(' when called. func (p *Parser) parseMRPatternBody() (*types.PatternNode, error) { if t := p.lexer.NextToken(); t.Type != TokenLParen { return nil, fmt.Errorf("expected '(' after PATTERN, got %q", t.Value) @@ -44,7 +44,7 @@ func (p *Parser) parseMRAlternation() (*types.PatternNode, error) { return &types.PatternNode{Kind: types.PatternAlternation, Children: children}, nil } -// parseMRSequence: quantified+ (并列即序列) +// parseMRSequence: quantified+ (Sequence equals sequence) func (p *Parser) parseMRSequence() (*types.PatternNode, error) { var atoms []*types.PatternNode for isMRAtomStart(p.peekToken()) { @@ -63,7 +63,7 @@ func (p *Parser) parseMRSequence() (*types.PatternNode, error) { return &types.PatternNode{Kind: types.PatternSequence, Children: atoms}, nil } -// parseMRQuantified: atom + 可选量词 +// parseMRQuantified: atom + optional quantifier func (p *Parser) parseMRQuantified() (*types.PatternNode, error) { atom, err := p.parseMRAtom() if err != nil { @@ -79,8 +79,8 @@ func (p *Parser) parseMRQuantified() (*types.PatternNode, error) { return &types.PatternNode{Kind: types.PatternRepetition, Children: []*types.PatternNode{atom}, Quant: q}, nil } -// parseMRAtom: 模式变量 | ( 交替 ) | PERMUTE(...) | {- 排除 -} -// 模式变量可为任意标识符,含被词法器归为关键字的词(如 End/When),故按"值以字母开头"识别。 +// parseMRAtom: Pattern variable | (alternate) | PERMUTE(...) | {- Exclude -} +// The pattern variable can be any identifier, including words that are categorized as keywords (such as End/When), so they are identified by "values starting with letters." func (p *Parser) parseMRAtom() (*types.PatternNode, error) { t := p.peekToken() switch t.Type { @@ -95,7 +95,7 @@ func (p *Parser) parseMRAtom() (*types.PatternNode, error) { } return &types.PatternNode{Kind: types.PatternGroup, Children: []*types.PatternNode{inner}}, nil case TokenLBrace: - // {- ... -} 排除(absence):解析为 Exclusion 节点,编译期拒绝 + // {-... -} Absence: Resolves as an Exclusion node, compile rejection p.lexer.NextToken() // consume '{' if d := p.lexer.NextToken(); d.Type != TokenMinus { return nil, fmt.Errorf("expected '-' after '{' in exclusion pattern, got %q", d.Value) @@ -113,7 +113,7 @@ func (p *Parser) parseMRAtom() (*types.PatternNode, error) { return &types.PatternNode{Kind: types.PatternExclusion, Children: []*types.PatternNode{inner}}, nil } if isMRIdentLike(t) { - p.lexer.NextToken() // consume 变量名 + p.lexer.NextToken() // consume variable name if t.Type == TokenIdent && strings.EqualFold(t.Value, "PERMUTE") { return p.parseMRPermute() } @@ -122,8 +122,8 @@ func (p *Parser) parseMRAtom() (*types.PatternNode, error) { return nil, fmt.Errorf("unexpected %q in PATTERN", t.Value) } -// isMRIdentLike 报告 token 是否像一个标识符(值以字母/下划线开头),用于把 End/When -// 等被词法器归为关键字的词也接受为模式变量。 +// isMRIdentLike reports whether the token acts like an identifier (values start with letters/underscores) used to set End/When +// Words that are classified as keywords by the lexical analyzer are also accepted as pattern variables. func isMRIdentLike(t Token) bool { v := t.Value return v != "" && (isLetter(v[0]) || v[0] == '_') @@ -152,7 +152,7 @@ func (p *Parser) parseMRPermute() (*types.PatternNode, error) { return &types.PatternNode{Kind: types.PatternPermute, Children: children}, nil } -// isMRAtomStart 报告 token 能否开启一个模式原子。 +// isMRAtomStart reports whether the token can enable a mode atom. func isMRAtomStart(t Token) bool { if t.Type == TokenLParen || t.Type == TokenLBrace { return true @@ -160,7 +160,7 @@ func isMRAtomStart(t Token) bool { return isMRIdentLike(t) } -// tryMRQuantifier 尝试读取后缀量词;ok=false 表示无量词。 +// tryMRQuantifier attempts to read the suffix quantifier; ok=false means no quantifier. func (p *Parser) tryMRQuantifier() (*types.Quantifier, bool, error) { t := p.peekToken() var q types.Quantifier @@ -188,7 +188,7 @@ func (p *Parser) tryMRQuantifier() (*types.Quantifier, bool, error) { return &q, true, nil } -// parseMRBounded 解析 {n} / {n,} / {n,m}('{' 已消费)。 +// parseMRBounded parse {n} / {n,} / {n,m} ('{' consumed). func (p *Parser) parseMRBounded() (types.Quantifier, error) { nTok := p.lexer.NextToken() if nTok.Type != TokenNumber { @@ -225,7 +225,7 @@ func (p *Parser) parseMRBounded() (types.Quantifier, error) { return types.Quantifier{}, fmt.Errorf("expected ',' or '}' in quantifier, got %q", t.Value) } -// consumeReluctant 若量词后紧跟 '?' 则消费它,返回 true(懒惰量词)。 +// consumeReluctant: If the quantifier is immediately followed by '?', it is consumed and returns true (laziness quantifier). func (p *Parser) consumeReluctant() bool { if p.peekToken().Type == TokenQuestion { p.lexer.NextToken() diff --git a/rsql/parser_match_test.go b/rsql/parser_match_test.go index c45317e..a75f19f 100644 --- a/rsql/parser_match_test.go +++ b/rsql/parser_match_test.go @@ -7,7 +7,7 @@ import ( "github.com/rulego/streamsql/types" ) -// 解析一条 MATCH_RECOGNIZE 查询,返回 config 与 spec(断言解析成功)。 +// Parse a MATCH_RECOGNIZE query, returning config and spec (asserting successful resolution). func mustParseMR(t *testing.T, sql string) (*types.Config, *types.MatchRecognizeSpec) { t.Helper() cfg, _, err := Parse(sql) @@ -62,10 +62,10 @@ func TestMR_BasicStructure(t *testing.T) { func TestMR_PatternQuantifiers(t *testing.T) { cases := []struct { - pat string - kind types.PatternKind - min int - max int + pat string + kind types.PatternKind + min int + max int }{ {`A{3}`, types.PatternRepetition, 3, 3}, {`A{2,}`, types.PatternRepetition, 2, -1}, @@ -156,7 +156,7 @@ func TestMR_WithinUnits(t *testing.T) { } } -// 错误路径:缺 PATTERN、缺 ORDER BY、与 GROUP BY 冲突(在 ToStreamConfig 期 fail-fast)。 +// Error path: missing PATTERN, missing ORDER BY, conflicting with GROUP BY (fail-fast during ToStreamConfig). func TestMR_Errors(t *testing.T) { cases := []struct { name string @@ -176,8 +176,8 @@ func TestMR_Errors(t *testing.T) { } } -// 排除模式 {- -} 在 parser 层识别为 PatternExclusion(设计:parser 识别全语法), -// 编译期由 cep.Validate 拒绝(见 cep 包测试)。 +// The exclusion pattern {- -} is recognized as PatternExclusion at the parser layer (design: parser recognition syntax is complete), +// The compilation period is by cep.Validate rejects (see cep package testing). func TestMR_ExclusionParsed(t *testing.T) { sql := `SELECT * FROM stream MATCH_RECOGNIZE (ORDER BY ts PATTERN ({- A -}) DEFINE A AS v>0)` _, mr := mustParseMR(t, sql) @@ -186,4 +186,4 @@ func TestMR_ExclusionParsed(t *testing.T) { } } -// EmitSync 拒绝 CEP 查询的行为在 stream 层(见 e2e/cep_test 与 streamsql.EmitSync)。 +// EmitSync rejects CEP queries at the stream layer (see e2e/cep_test and streamsql.EmitSync). diff --git a/rsql/parser_test.go b/rsql/parser_test.go index d3c0567..d13c188 100644 --- a/rsql/parser_test.go +++ b/rsql/parser_test.go @@ -6,7 +6,7 @@ import ( "testing" ) -// TestNewParser 测试解析器的创建 +// TestNewParser Creation of the test parser func TestNewParser(t *testing.T) { input := "SELECT * FROM table" parser := NewParser(input) @@ -28,11 +28,11 @@ func TestNewParser(t *testing.T) { } } -// TestParserGetErrors 测试错误获取功能 +// TestParserGetErrors test error retrieval func TestParserGetErrors(t *testing.T) { - // 使用一个明显无效的SQL,确保会产生错误 + // Using an obviously invalid SQL ensures errors occur parser := NewParser("SELECT * FROM table WHERE INVALID_FUNCTION()") - _, err := parser.Parse() // 这会产生错误 + _, err := parser.Parse() // This leads to errors if err == nil { t.Error("Expected parser to have errors") } @@ -46,7 +46,7 @@ func TestParserGetErrors(t *testing.T) { } } -// TestParserBasicSelect 测试基本SELECT语句解析 +// TestParserBasicSelect Tests basic SELECT statement parsing func TestParserBasicSelect(t *testing.T) { tests := []struct { input string @@ -83,9 +83,9 @@ func TestParserBasicSelect(t *testing.T) { } } -// TestParserFieldParsing 测试字段解析 +// TestParserFieldParsing Test field parsing func TestParserFieldParsing(t *testing.T) { - // 测试简单字段 + // Test simple fields t.Run("simple fields", func(t *testing.T) { sql := "SELECT name, age, city FROM users" parser := NewParser(sql) @@ -106,7 +106,7 @@ func TestParserFieldParsing(t *testing.T) { } }) - // 测试带别名的字段 + // Test fields with aliases t.Run("fields with aliases", func(t *testing.T) { sql := "SELECT name AS full_name, age AS years FROM users" parser := NewParser(sql) @@ -127,7 +127,7 @@ func TestParserFieldParsing(t *testing.T) { } }) - // 测试聚合函数字段 + // Test the numeric segment of the aggregate function t.Run("aggregate function fields", func(t *testing.T) { sql := "SELECT COUNT(*), SUM(amount), AVG(price) FROM orders" parser := NewParser(sql) @@ -148,7 +148,7 @@ func TestParserFieldParsing(t *testing.T) { } }) - // 测试复杂表达式字段 + // Test complex expression fields t.Run("complex expression fields", func(t *testing.T) { sql := "SELECT price * quantity AS total, UPPER(name) AS upper_name FROM products" parser := NewParser(sql) @@ -170,9 +170,9 @@ func TestParserFieldParsing(t *testing.T) { }) } -// TestParserWindowFunctionParsing 测试窗口函数解析 +// TestParserWindowFunctionParsing Analysis of the test window function func TestParserWindowFunctionParsing(t *testing.T) { - // 测试基本窗口相关语法(不使用OVER函数,因为解析器不支持) + // Test basic window syntax (do not use the OVER function, as the parser does not support it) t.Run("basic window function", func(t *testing.T) { sql := "SELECT name, COUNT(*) FROM employees GROUP BY name ORDER BY COUNT(*) DESC" parser := NewParser(sql) @@ -181,13 +181,13 @@ func TestParserWindowFunctionParsing(t *testing.T) { if err != nil { t.Errorf("Parse() error = %v", err) } - // 验证基本的聚合和排序功能 + // Verify basic aggregation and sorting functions if len(stmt.GroupBy) == 0 { t.Error("Expected GROUP BY to be parsed") } }) - // 测试带聚合的查询(替代窗口函数) + // Testing queries with aggregation (alternative window functions) t.Run("window function with partition by", func(t *testing.T) { sql := "SELECT department, COUNT(*) FROM employees GROUP BY department ORDER BY COUNT(*) DESC" parser := NewParser(sql) @@ -196,13 +196,13 @@ func TestParserWindowFunctionParsing(t *testing.T) { if err != nil { t.Errorf("Parse() error = %v", err) } - // 验证分组功能 + // Validate grouping functionality if len(stmt.GroupBy) == 0 { t.Error("Expected GROUP BY to be parsed") } }) - // 测试多个聚合函数 + // Test multiple aggregate functions t.Run("multiple window functions", func(t *testing.T) { sql := "SELECT name, COUNT(*), SUM(salary) FROM employees GROUP BY name" parser := NewParser(sql) @@ -217,9 +217,9 @@ func TestParserWindowFunctionParsing(t *testing.T) { }) } -// TestParserGroupByParsing 测试GROUP BY解析 +// TestParserGroupByParsing Testing GROUP BY analysis func TestParserGroupByParsing(t *testing.T) { - // 测试单个GROUP BY字段 + // Test individual GROUP BY fields t.Run("single group by field", func(t *testing.T) { sql := "SELECT category, COUNT(*) FROM products GROUP BY category" parser := NewParser(sql) @@ -236,7 +236,7 @@ func TestParserGroupByParsing(t *testing.T) { } }) - // 测试多个GROUP BY字段 + // Test multiple GROUP BY fields t.Run("multiple group by fields", func(t *testing.T) { sql := "SELECT category, region, COUNT(*) FROM products GROUP BY category, region" parser := NewParser(sql) @@ -256,9 +256,9 @@ func TestParserGroupByParsing(t *testing.T) { }) } -// TestParserLimitParsing 测试LIMIT解析 +// TestParserLimitParsing Testing LIMIT analysis func TestParserLimitParsing(t *testing.T) { - // 测试正常的LIMIT值 + // Test the normal LIMIT value t.Run("normal limit value", func(t *testing.T) { sql := "SELECT name FROM users LIMIT 100" parser := NewParser(sql) @@ -272,7 +272,7 @@ func TestParserLimitParsing(t *testing.T) { } }) - // 测试LIMIT 0 + // Test LIMIT 0 t.Run("limit zero", func(t *testing.T) { sql := "SELECT name FROM users LIMIT 0" parser := NewParser(sql) @@ -286,7 +286,7 @@ func TestParserLimitParsing(t *testing.T) { } }) - // 测试大的LIMIT值 + // Test the large LIMIT value t.Run("large limit value", func(t *testing.T) { sql := "SELECT name FROM users LIMIT 999999" parser := NewParser(sql) @@ -301,9 +301,9 @@ func TestParserLimitParsing(t *testing.T) { }) } -// TestParserWhereClauseParsing 测试WHERE子句解析 +// TestParserWhereClauseParsing tests the parsing of the WHERE clause func TestParserWhereClauseParsing(t *testing.T) { - // 测试简单的WHERE条件 + // Test simple WHERE conditions t.Run("simple where condition", func(t *testing.T) { sql := "SELECT name FROM users WHERE age = 25" parser := NewParser(sql) @@ -317,7 +317,7 @@ func TestParserWhereClauseParsing(t *testing.T) { } }) - // 测试复杂的WHERE条件 + // Test complex WHERE conditions t.Run("complex where condition", func(t *testing.T) { sql := "SELECT name FROM users WHERE age > 18 AND city = 'New York' OR status = 'active'" parser := NewParser(sql) @@ -332,7 +332,7 @@ func TestParserWhereClauseParsing(t *testing.T) { } }) - // 测试带函数的WHERE条件 + // Test the WHERE condition with the function t.Run("where condition with functions", func(t *testing.T) { sql := "SELECT name FROM users WHERE UPPER(name) LIKE 'JOHN%'" parser := NewParser(sql) @@ -348,9 +348,9 @@ func TestParserWhereClauseParsing(t *testing.T) { }) } -// TestParserEnhancedCoverage 增强Parser的测试覆盖率 +// TestParserEnhancedCoverage Enhances parser's test coverage func TestParserEnhancedCoverage(t *testing.T) { - // 测试基本的Parser创建和错误处理 + // Testing basic parser creation and error handling t.Run("parser creation and error handling", func(t *testing.T) { sql := "SELECT * FROM test" parser := NewParser(sql) @@ -358,7 +358,7 @@ func TestParserEnhancedCoverage(t *testing.T) { t.Error("NewParser() returned nil") } - // 测试初始状态 + // Test the initial state if parser.HasErrors() { t.Error("New parser should not have errors") } @@ -369,7 +369,7 @@ func TestParserEnhancedCoverage(t *testing.T) { } }) - // 测试解析简单的SELECT语句 + // Test parses simple SELECT statements t.Run("parse simple select", func(t *testing.T) { sql := "SELECT name, age FROM users" parser := NewParser(sql) @@ -389,7 +389,7 @@ func TestParserEnhancedCoverage(t *testing.T) { } }) - // 测试解析SELECT * + // Test Analysis SELECT * t.Run("parse select all", func(t *testing.T) { sql := "SELECT * FROM products" parser := NewParser(sql) @@ -398,8 +398,8 @@ func TestParserEnhancedCoverage(t *testing.T) { if err != nil { t.Errorf("Parse() error = %v", err) } - // SELECT * 应该设置SelectAll为true,但当前实现可能不同 - // 检查是否正确解析了*字段 + // SELECT * should set SelectAll to true, but the current implementation may differ + // Check whether the * field has been resolved correctly if len(stmt.Fields) == 0 || stmt.Fields[0].Expression != "*" { t.Error("Expected * field to be parsed") } @@ -408,7 +408,7 @@ func TestParserEnhancedCoverage(t *testing.T) { } }) - // 测试解析SELECT DISTINCT + // Test analysis SELECT DISTINCT t.Run("parse select distinct", func(t *testing.T) { sql := "SELECT DISTINCT category FROM products" parser := NewParser(sql) @@ -428,7 +428,7 @@ func TestParserEnhancedCoverage(t *testing.T) { } }) - // 测试解析带WHERE子句的SELECT语句 + // Test parsing SELECT statements with WHERE clauses t.Run("parse select with where", func(t *testing.T) { sql := "SELECT name FROM users WHERE age > 18" parser := NewParser(sql) @@ -442,7 +442,7 @@ func TestParserEnhancedCoverage(t *testing.T) { } }) - // 测试解析带GROUP BY的SELECT语句 + // Test and parse SELECT statements with GROUP BY t.Run("parse select with group by", func(t *testing.T) { sql := "SELECT category, COUNT(*) FROM products GROUP BY category" parser := NewParser(sql) @@ -459,7 +459,7 @@ func TestParserEnhancedCoverage(t *testing.T) { } }) - // 测试解析带HAVING的SELECT语句 + // Test parsing SELECT statements with HAVING t.Run("parse select with having", func(t *testing.T) { sql := "SELECT category, COUNT(*) FROM products GROUP BY category HAVING COUNT(*) > 5" parser := NewParser(sql) @@ -473,7 +473,7 @@ func TestParserEnhancedCoverage(t *testing.T) { } }) - // 测试解析带LIMIT的SELECT语句 + // Test and parse SELECT statements with LIMIT t.Run("parse select with limit", func(t *testing.T) { sql := "SELECT name FROM users LIMIT 10" parser := NewParser(sql) @@ -487,7 +487,7 @@ func TestParserEnhancedCoverage(t *testing.T) { } }) - // 测试解析简单的窗口相关语句(避免复杂的窗口函数语法) + // Test parses of simple window-related statements (avoiding complex window function syntax) t.Run("parse select with window function", func(t *testing.T) { sql := "SELECT name, COUNT(*) FROM employees GROUP BY name" parser := NewParser(sql) @@ -499,13 +499,13 @@ func TestParserEnhancedCoverage(t *testing.T) { if stmt == nil { t.Error("Expected statement to be parsed") } - // 验证基本的GROUP BY解析 + // Verify basic GROUP BY resolution if len(stmt.GroupBy) != 1 || stmt.GroupBy[0] != "name" { t.Error("Expected GROUP BY name to be parsed") } }) - // 测试解析复杂的SELECT语句 + // Test parses complex SELECT statements t.Run("parse complex select", func(t *testing.T) { sql := "SELECT DISTINCT category, SUM(price) as total FROM products WHERE price > 100 GROUP BY category HAVING SUM(price) > 1000 LIMIT 5" parser := NewParser(sql) @@ -532,9 +532,9 @@ func TestParserEnhancedCoverage(t *testing.T) { }) } -// TestParserErrorHandling 测试Parser的错误处理 +// TestParserErrorHandling tests Parser's error handling func TestParserErrorHandling(t *testing.T) { - // 测试无效的SQL语句 + // Testing for invalid SQL statements t.Run("invalid sql syntax", func(t *testing.T) { sql := "INVALID SQL STATEMENT" parser := NewParser(sql) @@ -546,13 +546,13 @@ func TestParserErrorHandling(t *testing.T) { if stmt != nil { t.Error("Expected nil statement for invalid SQL") } - // 检查是否有错误(某些解析器可能不实现HasErrors方法) + // Check for errors (some parsers may not implement the HasErrors method) if err == nil { t.Error("Expected error for invalid SQL") } }) - // 测试空的SQL语句 + // Test the empty SQL statement t.Run("empty sql", func(t *testing.T) { sql := "" parser := NewParser(sql) @@ -566,7 +566,7 @@ func TestParserErrorHandling(t *testing.T) { } }) - // 测试缺少FROM子句的SELECT语句 + // Test the SELECT statement missing the FROM clause t.Run("missing from clause", func(t *testing.T) { sql := "SELECT name" parser := NewParser(sql) @@ -575,14 +575,14 @@ func TestParserErrorHandling(t *testing.T) { if err == nil { t.Error("Expected error for missing FROM clause") } - // 某些解析器可能允许没有FROM子句的SELECT - // 只检查是否有错误 + // Some parsers may allow SELECT without a FROM clause + // Only check for errors if err == nil && stmt == nil { t.Error("Expected either error or valid statement") } }) - // 测试无效的LIMIT值 + // Test for invalid LIMIT values t.Run("invalid limit value", func(t *testing.T) { sql := "SELECT name FROM users LIMIT abc" parser := NewParser(sql) @@ -591,27 +591,27 @@ func TestParserErrorHandling(t *testing.T) { if err == nil { t.Error("Expected error for invalid LIMIT value") } - // 某些解析器可能有不同的LIMIT处理方式 - // 只检查是否有错误 + // Some parsers may have different LIMIT handling methods + // Only check for errors if err == nil && stmt == nil { t.Error("Expected either error or valid statement") } }) - // 测试HAVING子句但没有GROUP BY + // Testing HAVING clause but no GROUP BY t.Run("having without group by", func(t *testing.T) { sql := "SELECT name FROM users HAVING COUNT(*) > 5" parser := NewParser(sql) stmt, err := parser.Parse() - // 这可能是有效的或无效的,取决于实现 - // 如果实现要求HAVING必须与GROUP BY一起使用,则应该有错误 + // This may be effective or ineffective, depending on the implementation + // If the implementation requires HAVING to be used with GROUP BY, there should be an error _ = stmt _ = err }) } -// TestParserErrorRecovery 测试错误恢复功能 +// TestParserErrorRecovery: Test error recovery function func TestParserErrorRecovery(t *testing.T) { tests := []struct { input string @@ -629,19 +629,19 @@ func TestParserErrorRecovery(t *testing.T) { parser := NewParser(test.input) _, err := parser.Parse() - // 对于 "SELECT FROM table" 这种情况,可能不会产生错误,因为解析器可能会将其解释为有效的语法 + // For the case of "SELECT FROM table"," errors may not occur, because the parser may interpret it as valid syntax if test.input == "SELECT FROM table" { - // 这种情况下,我们不强制要求有错误 + // In such cases, we do not force errors return } - // 应该有错误 + // There should be errors if err == nil && !parser.HasErrors() { t.Errorf("Expected error but got none for input: %s", test.input) return } - // 检查是否记录了错误 + // Check if any errors have been recorded if !parser.HasErrors() { t.Errorf("Expected errors to be recorded for input: %s", test.input) } @@ -649,7 +649,7 @@ func TestParserErrorRecovery(t *testing.T) { } } -// TestParseBasicSQL 测试基本SQL解析功能 +// TestParseBasicSQL tests basic SQL parsing functions func TestParseBasicSQL(t *testing.T) { tests := []struct { name string @@ -689,11 +689,11 @@ func TestParseBasicSQL(t *testing.T) { if err != nil { t.Errorf("Unexpected error for %s: %v", test.sql, err) } else { - // 基本验证 + // Basic verification if config == nil { t.Errorf("Expected config but got nil for %s", test.sql) } - // condition可以为空 + // 'condition' can be emptiness _ = condition } } @@ -701,7 +701,7 @@ func TestParseBasicSQL(t *testing.T) { } } -// TestRSQLIntegration 测试RSQL包的集成功能 +// TestRSQLIntegration Tests the integration functionality of RSQL packages func TestRSQLIntegration(t *testing.T) { tests := []struct { name string @@ -776,7 +776,7 @@ func TestRSQLIntegration(t *testing.T) { } } -// TestEdgeCases 测试边界情况 +// TestEdgeCases tests boundary conditions func TestEdgeCases(t *testing.T) { tests := []struct { name string @@ -805,7 +805,7 @@ func TestEdgeCases(t *testing.T) { { name: "VeryLongFieldList", input: "SELECT " + strings.Repeat("field, ", 10) + "field FROM table", - expectError: false, // 改回false,因为这应该是有效的SQL + expectError: false, // Change it back to false, because this should be valid SQL description: "长字段列表", }, } @@ -828,7 +828,7 @@ func TestEdgeCases(t *testing.T) { } } -// TestParserAdvancedFeatures 测试解析器的高级功能 +// TestParserAdvancedFeatures Advanced features of the test parser func TestParserAdvancedFeatures(t *testing.T) { tests := []struct { name string @@ -885,7 +885,7 @@ func TestParserAdvancedFeatures(t *testing.T) { } } -// TestComplexQueries 测试复杂查询 +// TestComplexQueries tests complex queries func TestComplexQueries(t *testing.T) { tests := []struct { name string @@ -921,9 +921,9 @@ func TestComplexQueries(t *testing.T) { } } -// TestParserPerformance 测试解析器性能 +// TestParserPerformance Tests the parser performance func TestParserPerformance(t *testing.T) { - // 测试大量解析操作的性能 + // Testing the performance of large-scale parsing operations for i := 0; i < 1000; i++ { sql := "SELECT field1, field2, field3 FROM table WHERE condition = 'value'" parser := NewParser(sql) @@ -935,7 +935,7 @@ func TestParserPerformance(t *testing.T) { } } -// TestParserConcurrency 测试解析器并发安全性 +// TestParserConcurrency tests the parser for concurrency security func TestParserConcurrency(t *testing.T) { const numGoroutines = 10 const numIterations = 10 @@ -956,15 +956,15 @@ func TestParserConcurrency(t *testing.T) { }(i) } - // 等待所有goroutines完成 + // Wait for all the goroutines to be completed for i := 0; i < numGoroutines; i++ { <-done } } -// TestParserMemoryUsage 测试内存使用情况 +// TestParserMemoryUsage tests memory usage func TestParserMemoryUsage(t *testing.T) { - // 测试大量解析操作不会导致内存泄漏 + // Testing large numbers of parsing operations will not cause memory leaks for i := 0; i < 1000; i++ { sql := "SELECT field1, field2, field3 FROM table WHERE condition = 'value'" parser := NewParser(sql) @@ -976,7 +976,7 @@ func TestParserMemoryUsage(t *testing.T) { } } -// TestParserWithDifferentInputSizes 测试不同输入大小的解析 +// TestParserWithDifferentInputSizes tests the parsing of different input sizes func TestParserWithDifferentInputSizes(t *testing.T) { tests := []struct { name string @@ -986,7 +986,7 @@ func TestParserWithDifferentInputSizes(t *testing.T) { { name: "VeryShort", input: "SELECT 1", - expectError: true, // 缺少FROM子句 + expectError: true, // Missing the FROM clause }, { name: "Short", diff --git a/rsql/statettl_test.go b/rsql/statettl_test.go index 347bdb7..bb0433b 100644 --- a/rsql/statettl_test.go +++ b/rsql/statettl_test.go @@ -8,7 +8,7 @@ import ( "github.com/stretchr/testify/require" ) -// TestParseStateTTL: STATETTL='24h' 解析到 WindowConfig.CountStateTTL。 +// TestParseStateTTL: STATETTL='24h' Resolves to WindowConfig.CountStateTTL. func TestParseStateTTL(t *testing.T) { config, _, err := Parse("SELECT deviceId, COUNT(*) FROM stream GROUP BY deviceId, CountingWindow(10) WITH(STATETTL='24h')") require.NoError(t, err) @@ -16,7 +16,7 @@ func TestParseStateTTL(t *testing.T) { assert.Equal(t, 24*time.Hour, config.WindowConfig.CountStateTTL) } -// TestParseStateTTL_DefaultZero: 无 STATETTL 时默认 0(禁用)。 +// TestParseStateTTL_DefaultZero: Defaults to 0 (disabled) when STATETTL is absent. func TestParseStateTTL_DefaultZero(t *testing.T) { config, _, err := Parse("SELECT deviceId, COUNT(*) FROM stream GROUP BY deviceId, CountingWindow(10)") require.NoError(t, err) diff --git a/stream/analytic.go b/stream/analytic.go index 7ea4742..29a1090 100644 --- a/stream/analytic.go +++ b/stream/analytic.go @@ -20,44 +20,44 @@ import ( // least-recently-used partition is evicted (state + cached last-result). const defaultMaxPartitions = 10000 -// AnalyticEngine 管理一条查询里所有分析函数字段的流级状态机。 -// 走直连路径(EmitSync/processDirectData):每条事件在 WHERE 之前求值,结果按 alias -// 注入行(供 WHERE/投影引用)。状态按 PARTITION 分桶,WHEN 控制状态更新。 +// AnalyticEngine manages the stream-level state machine for all numeric segments of the analysis function in a query. +// Direct connection path (EmitSync/processDirectData): Each event evaluates before WHERE and is followed by alias +// Inject lines (for WHERE/projection reference). Status is bucketed by PARTITION, and status updates are controlled when WHEN is maintained. type AnalyticEngine struct { owner *Stream fields []*analyticFieldEngine } -// HasFields 是否有分析函数字段(nil 引擎视为无)。 +// Does HasFields have numeric paragraphs for analysis functions (nil engines consider none). func (e *AnalyticEngine) HasFields() bool { return e != nil && len(e.fields) > 0 } -// analyticFieldEngine 单个分析函数字段的状态机(含 PARTITION 分桶 + WHEN 条件)。 -// 一个字段可含多个分析调用(如 acc_max(v) - acc_min(v)),每个调用独立状态机、共享分区。 +// analyticFieldEngine is a state machine with several segments of a single analysis function (including PARTITION buckets + WHEN condition). +// A field can contain multiple analysis calls (e.g., acc_max(v) - acc_min(v)), each call independent state machine and shared partition. type analyticFieldEngine struct { af types.AnalyticField - stateCtors []func() functions.AnalyticState // 每个分析调用一个状态构造器 - whenCond condition.Condition // WHEN,nil 表示无 + stateCtors []func() functions.AnalyticState // Each analysis calls a state initializer + whenCond condition.Condition // WHEN, nil means none mu sync.Mutex - noPart []functions.AnalyticState // 无 PARTITION 时的 per-call 状态 - partitions map[string]*list.Element // PARTITION BY 时 per-key 状态(LRU 节点) - lru *list.List // LRU 顺序:front=最近使用,back=待淘汰 - lastResults map[string]any // per-partition 上次结果(WHEN 不满足时复用) - maxPartitions int // 分区数上限(超出按 LRU 淘汰) - wrapperParsed *expr.Expression // wrapper 的 expr 包解析缓存(仅 bridge 失败时用,如 CASE),每字段解析一次 + noPart []functions.AnalyticState // Per-call state without PARTITION + partitions map[string]*list.Element // Per-key state during PARTITION BY (LRU node) + lru *list.List // LRU order: front = recently used, back = to be eliminated + lastResults map[string]any // per-partition Last result (reused WHEN not met) + maxPartitions int // Upper limit on the number of partitions (exceeding this is eliminated by LRU) + wrapperParsed *expr.Expression // The wrapper's expr packet parsing cache (used only when bridge fails, e.g., CASE), parsing each field once } -// partitionEntry 是 LRU 链表节点携带的分区状态(每个分析调用一个)。 +// partitionEntry is the partition state carried by the LRU linked list node (one per analysis call). type partitionEntry struct { key string states []functions.AnalyticState } -// NewAnalyticEngine 根据配置构建分析函数状态机集合。无分析函数时返回 nil。 +// NewAnalyticEngine builds a set of state machines for analysis functions based on the configuration. Returns nil when no analysis function is present. func NewAnalyticEngine(owner *Stream, fields []types.AnalyticField) (*AnalyticEngine, error) { if len(fields) == 0 { return nil, nil } - // 分区上限:选项注入(≤0 用默认)。 + // Partition limit: option injection (≤0 uses default). maxPart := defaultMaxPartitions if owner != nil && owner.config.AnalyticMaxPartitions > 0 { maxPart = owner.config.AnalyticMaxPartitions @@ -88,8 +88,8 @@ func NewAnalyticEngine(owner *Stream, fields []types.AnalyticField) (*AnalyticEn return &AnalyticEngine{owner: owner, fields: engines}, nil } -// buildStateCtors 为字段每个分析调用构造状态构造器。Calls 为空时(如 WHERE 占位符调用, -// 只设了 FuncName)退化为单调用。 +// buildStateCtors is a field for each analysis call to construct the state initializer. Calls when empty (such as when WHERE placeholder calls, +// Only FuncName is set) degraded to single call. func buildStateCtors(af types.AnalyticField) ([]func() functions.AnalyticState, error) { names := make([]string, 0, len(af.Calls)+1) for _, c := range af.Calls { @@ -113,7 +113,7 @@ func buildStateCtors(af types.AnalyticField) ([]func() functions.AnalyticState, return ctors, nil } -// Evaluate 对一行求值所有分析函数字段,返回 map[alias]value。 +// Evaluate Evaluates all numeric segments of the analysis function on a single line, returning map[alias]value. func (e *AnalyticEngine) Evaluate(row map[string]any) map[string]any { out := make(map[string]any, len(e.fields)) for _, fe := range e.fields { @@ -125,21 +125,21 @@ func (e *AnalyticEngine) Evaluate(row map[string]any) map[string]any { func (fe *analyticFieldEngine) evaluate(s *Stream, row map[string]any) (result any) { defer func() { if r := recover(); r != nil { - // 单个分析函数解析/求值异常不应中断整条流水线。 + // Abnormal parsing/evaluation of a single analysis function should not interrupt the entire pipeline. if s != nil && s.log != nil { s.log.Error("analytic %q evaluate panic: %v", fe.af.Expression, r) } result = nil } }() - // 多列函数(changed_cols)走 ApplyColumns 扇出分支。 + // Multi-column functions (changed_cols) go through ApplyColumns to fan out branches. if fe.af.MultiColumn { return fe.evaluateMultiColumn(s, row) } partKey := fe.partitionKey(row) fe.mu.Lock() defer fe.mu.Unlock() - // WHEN:满足才更新状态,否则复用上次结果。 + // WHEN: Only update the status when the condition is satisfied; otherwise, the previous result will be reused. if fe.whenCond != nil && !fe.whenCond.Evaluate(row) { if last, ok := fe.lastResults[partKey]; ok { return last @@ -149,10 +149,10 @@ func (fe *analyticFieldEngine) evaluate(s *Stream, row map[string]any) (result a states := fe.getStateLocked(partKey) calls := fe.af.Calls if len(calls) == 0 { - // WHERE 占位符调用等只设了 FuncName/Expression/Args,退化为单调用。 + // WHERE placeholder calls and similar functions only set FuncName/Expression/Args, degrading to single calls. calls = []types.AnalyticCall{{FuncName: fe.af.FuncName, BareCall: fe.af.Expression, Args: fe.af.Args}} } - // had_changed(true, *) 按列名比较整行,避免行 schema 变化(列增删/乱序)的位置错位。 + // had_changed(true, *) Compares entire rows by column name to avoid misalignment caused by schema changes (column addition, deletion, or disorder of rows). if fe.af.FuncName == "had_changed" && hasStarArg(fe.af.Args) { if named, ok := states[0].(functions.NamedRowState); ok { ignoreNull := false @@ -164,15 +164,15 @@ func (fe *analyticFieldEngine) evaluate(s *Stream, row map[string]any) (result a return result } } - // 纯单调用字段(无外层表达式):直接返回首个调用结果。 + // Single-call field (no outer expression): Returns the first call result directly. if fe.af.WrapperExpr == "" { result = fe.applyCall(s, row, calls[0], states[0]) fe.lastResults[partKey] = result return result } - // 表达式包分析函数(单或多调用):每调用 Apply 推进各自状态,各结果(含 nil)注入占位, - // 再求 wrapper。nil 占位交给 wrapper 自行处理:coalesce(__analytic_self__,-1)→-1、 - // CASE WHEN __analytic_self__>... 走 ELSE;算术 __analytic_self__-x 在 nil 上失败→null 传播返回 nil。 + // Expression package analysis function (single or multiple calls): Each call to Apply advances its respective state, and each result (including nil) is injected into a placeholder, + // Then find the wrapper. nil spaceholder is left to the wrapper to handle: coalesce(__analytic_self__,-1)→-1, + // CASE WHEN __analytic_self__>... Go ELSE; Arithmetic __analytic_self__-x fails on nil→ null propagation returns nil. rowCopy := make(map[string]any, len(row)+len(calls)) for k, v := range row { rowCopy[k] = v @@ -187,8 +187,8 @@ func (fe *analyticFieldEngine) evaluate(s *Stream, row map[string]any) (result a } v, isNull, err := fe.evalWrapper(rowCopy) if err != nil { - // 任一分析为 nil 时,算术等不支持 nil 操作数的 wrapper 失败属正常 null 传播,静默返回 nil; - // 全非 nil 却失败才是真异常,记日志。 + // When any analysis is nil, the failure of a wrapper that does not support nil operands in arithmetic or other operations is normal null propagation and returns nil silently; + // Failing at NIL is the real anomaly—log this. if !anyNil && s != nil && s.log != nil { s.log.Error("analytic wrapper %q evaluate failed: %v", fe.af.WrapperExpr, err) } @@ -203,9 +203,9 @@ func (fe *analyticFieldEngine) evaluate(s *Stream, row map[string]any) (result a return v } -// evalWrapper 求值 wrapper 表达式:先走 expr bridge(函数/算术/字符串拼接), -// 失败则回退 expr 包(支持 CASE 等语句型表达式)。wrapper 每字段不变,故 expr 包解析 -// 结果按字段缓存(懒解析,调用方持 fe.mu 无竞态),避免逐行重复解析。isNull 区分显式 NULL 与 nil。 +// evalWrapper evaluates wrapper expression: first use expr bridge (function/arithmetic/string concatenation), +// If it fails, the expr package is reverted (supporting statements like CASE). wrapper keeps each field unchanged, so expr parses the package +// Results are cached by field (lazy parsing, caller holds fe.mu with no contention), avoiding repeated parsing line by line. isNull distinguishes between explicit NULL and nil. func (fe *analyticFieldEngine) evalWrapper(data map[string]any) (any, bool, error) { if v, err := functions.GetExprBridge().EvaluateExpression(fe.af.WrapperExpr, data); err == nil { return v, false, nil @@ -220,7 +220,7 @@ func (fe *analyticFieldEngine) evalWrapper(data map[string]any) (any, bool, erro return fe.wrapperParsed.EvaluateValueWithNull(data) } -// applyCall 求单个分析调用:解析参数(含 '*' 整行展开),应用到状态机。 +// applyCall for a single analysis call: parsing parameters (including '*' in full line expansion), applied to the state machine. func (fe *analyticFieldEngine) applyCall(s *Stream, row map[string]any, c types.AnalyticCall, state functions.AnalyticState) any { args, err := s.parseFunctionArgs(c.BareCall, row) if err != nil || args == nil { @@ -232,13 +232,13 @@ func (fe *analyticFieldEngine) applyCall(s *Stream, row map[string]any, c types. return state.Apply(args) } -// evaluateMultiColumn 处理 changed_cols 等多列函数:按 prefix+列名 扇出变化列。 +// evaluateMultiColumn handles multi-column functions like changed_cols: fan out the variable column by prefix+ column name. func (fe *analyticFieldEngine) evaluateMultiColumn(s *Stream, row map[string]any) any { values, err := s.parseFunctionArgs(fe.af.Expression, row) if err != nil || values == nil { values = []any{} } - // 位置参数:优先用已求值;"*" 致解析失败时用字面量还原 prefix/ignoreNull。 + // Position parameters: prioritize the already evaluated; "*" Restore prefix/ignoreNull with literal value when parsing fails. argVal := func(idx int) any { if idx < len(values) { return values[idx] @@ -253,7 +253,7 @@ func (fe *analyticFieldEngine) evaluateMultiColumn(s *Stream, row map[string]any cols := make(map[string]any) for i, nameExpr := range fe.af.Args[2:] { name := analyticColName(nameExpr) - if name == "*" { // changed_cols(prefix, ignoreNull, *) → 整行各列 + if name == "*" { // changed_cols(prefix, ignoreNull, *) → Columns of the entire row for k, v := range row { cols[k] = v } @@ -261,7 +261,7 @@ func (fe *analyticFieldEngine) evaluateMultiColumn(s *Stream, row map[string]any } valIdx := 2 + i if valIdx < len(values) { - // 窗口查询里内联聚合被重写为隐藏键:输出列名用显示名(如 avg → tavg)。 + // In window queries, inline aggregation is overridden to hidden keys: output column names are displayed names (e.g., avg → tavg). out := name if d, ok := fe.af.InlineAggDisplay[name]; ok { out = d @@ -288,7 +288,7 @@ func (fe *analyticFieldEngine) evaluateMultiColumn(s *Stream, row map[string]any return result } -// hasStarArg 判断参数表达式列表是否含 "*"(整行展开)。 +// hasStarArg checks whether the parameter expression list contains "*" (full line expansion). func hasStarArg(args []string) bool { for _, a := range args { if strings.TrimSpace(a) == "*" { @@ -298,10 +298,10 @@ func hasStarArg(args []string) bool { return false } -// expandStarArgs 把 "*" 之外的位置参数保留(如 ignoreNull),再追加整行各列的值, -// 供 had_changed(true, *) 等对整行求变化。args 为参数表达式,parsed 为已求值的位置值。 -// 行值按键名排序,保证跨事件位置稳定(否则 map 迭代序乱会导致误判变化)。 -// 当 parseFunctionArgs 因 "*" 失败导致 parsed 缺项时,用字面量还原标量参数。 +// expandStarArgs keeps position parameters outside "*" (such as ignoreNull), then adds values for each row of rows, +// Supply had_changed(true, *) and other parameters to find variation in the entire line. args is the parameter expression, parsed is the evaluated position value. +// Row values are sorted by keyname to ensure stable positions across events (otherwise, map iteration disorder may cause misjudgment changes). +// When parseFunctionArgs fails due to "*" causing parsed missing entries, restore scalar parameters with literals. func expandStarArgs(args []string, row map[string]any, parsed []any) []any { out := make([]any, 0, len(args)+len(row)) for i, a := range args { @@ -325,8 +325,8 @@ func expandStarArgs(args []string, row map[string]any, parsed []any) []any { return out } -// literalValue 解析标量字面量(true/false/数字/带引号字符串),用于 "*" 致解析失败时 -// 还原 ignoreNull 等参数。 +// literalValue parses scalar literals (true/false/number/quoted strings), used when "*" causes parsing failure +// Restores ignoreNull and other parameters. func literalValue(s string) any { s = strings.TrimSpace(s) switch s { @@ -347,7 +347,7 @@ func literalValue(s string) any { return s } -// analyticColName 从列名表达式取输出列名:去反引号/引号,取限定符最后一段。 +// analyticColName Retrieves the output column name from the column name expression: remove backquotes/quotes, and take the last paragraph of the qualifier. func analyticColName(expr string) string { s := strings.TrimSpace(expr) s = strings.Trim(s, "`") @@ -378,12 +378,12 @@ func (fe *analyticFieldEngine) getStateLocked(partKey string) []functions.Analyt return fe.noPart } if el, ok := fe.partitions[partKey]; ok { - fe.lru.MoveToFront(el) // 命中:提升为最近使用 + fe.lru.MoveToFront(el) // Hit: Upgraded to recently used return el.Value.(*partitionEntry).states } entry := &partitionEntry{key: partKey, states: newStates()} fe.partitions[partKey] = fe.lru.PushFront(entry) - // 超上限:淘汰最久未使用的分区,同步清理其 lastResults,防止内存泄漏。 + // Over-limit: Eliminates the longest unused partitions and synchronously cleans up their lastResults to prevent memory leaks. if fe.maxPartitions > 0 && fe.lru.Len() > fe.maxPartitions { if oldest := fe.lru.Back(); oldest != nil { oe := oldest.Value.(*partitionEntry) @@ -400,11 +400,11 @@ func (fe *analyticFieldEngine) partitionKey(row map[string]any) string { return "" } var sb strings.Builder - var lbuf [4]byte // 分区键片段长度(十进制,常见 < 1000) + var lbuf [4]byte // Partition key fragment length (decimal, common < 1000) for _, k := range fe.af.Over.PartitionBy { tk := typeKey(resolvePartitionField(row, k)) - // 长度前缀 + 尾分隔,避免值里含 '|' 或类型名导致跨列键碰撞。 - // 直接写 Builder,省去 fmt.Fprintf 的格式串解析。 + // Length prefix + suffix separator, preventing key collisions caused by values containing '|' or type names. + // Write Builder directly, skipping fmt.Fprintf format string parsing. lstr := strconv.AppendInt(lbuf[:0], int64(len(tk)), 10) sb.Write(lstr) sb.WriteByte(':') @@ -414,10 +414,10 @@ func (fe *analyticFieldEngine) partitionKey(row map[string]any) string { return sb.String() } -// resolvePartitionField 解析 PARTITION BY 键的实际值。裸列直查命中(deviceId/temp); -// JOIN 嵌套列(m.location,增强行里存为 row["m"]["location"])走 fieldpath; -// 窗口扁平限定列(stream.k,输出行按后缀 k 存)退 lookupRowField 后缀兜底。 -// 任一都不命中返回 nil(旧实现裸 row[k] 对限定列恒 nil → 全部落同一分区,静默错值)。 +// resolvePartitionField parses the actual value of the PARTITION BY key. Bare list direct lookup hit (deviceId/temp); +// JOIN Nested columns (m.location, stored in augmented row["m"]["location"]) via fieldpath; +// Window flat limit column (stream.k, output row stored by the suffix k) and remove the lookupRowField suffix as backup. +// None of these will hit the return nil (the old implementation of bare row[k] always placed nil → on the same partition with all restricted columns, resulting in mute errors). func resolvePartitionField(row map[string]any, key string) any { if v, ok := row[key]; ok { return v @@ -431,9 +431,9 @@ func resolvePartitionField(row map[string]any, key string) any { return nil } -// typeKey 生成 "类型|值" 形式的分区键片段,nil 记为 "nil|"。 -// 常见标量走类型 switch 手搓字符串,避免 fmt.Sprintf 的反射开销;其余类型 -// (切片/map/结构体等)退回 fmt.Sprintf 保证通用与 "%T|%v" 一致。 +// typeKey generates a partition key fragment in the form "type|value", with nil denoted as "nil|". +// Common scalar types are switched and manually twisted strings to avoid fmt.Sprintf reflection overhead; The rest of the types +// (Slices/maps/structs, etc.) Return fmt.Sprintf ensures that generality matches "%T|%v". func typeKey(v any) string { switch x := v.(type) { case nil: diff --git a/stream/analytic_test.go b/stream/analytic_test.go index 1ecdd4d..687736b 100644 --- a/stream/analytic_test.go +++ b/stream/analytic_test.go @@ -9,14 +9,14 @@ import ( "github.com/stretchr/testify/assert" ) -// D2:PARTITION 状态受 LRU 上限约束,超限淘汰最久未使用的分区(含其 lastResults)。 +// D2: The PARTITION state is constrained by the LRU limit; the most unused partition (including its lastResults) is eliminated when overrun. func TestAnalyticPartitionLRUEviction(t *testing.T) { fe := &analyticFieldEngine{ - af: types.AnalyticField{Over: &types.OverSpec{PartitionBy: []string{"k"}}}, - stateCtors: []func() functions.AnalyticState{func() functions.AnalyticState { return new(functions.LagFunction).NewState() }}, - partitions: make(map[string]*list.Element), - lru: list.New(), - lastResults: make(map[string]any), + af: types.AnalyticField{Over: &types.OverSpec{PartitionBy: []string{"k"}}}, + stateCtors: []func() functions.AnalyticState{func() functions.AnalyticState { return new(functions.LagFunction).NewState() }}, + partitions: make(map[string]*list.Element), + lru: list.New(), + lastResults: make(map[string]any), maxPartitions: 3, } @@ -27,13 +27,13 @@ func TestAnalyticPartitionLRUEviction(t *testing.T) { fe.getStateLocked(keyOf(v)) } - // 填满 3 个分区。 + // Fill 3 sections. touch(1) touch(2) touch(3) assert.Equal(t, 3, fe.lru.Len()) - // 加入第 4 个 → 淘汰最久未用的 k=1。 + // Add the 4th → to eliminate the oldest unused k=1. touch(4) assert.Equal(t, 3, fe.lru.Len(), "超出上限应按 LRU 淘汰") _, has1 := fe.partitions[keyOf(1)] @@ -43,7 +43,7 @@ func TestAnalyticPartitionLRUEviction(t *testing.T) { assert.True(t, ok, "k=%v 应保留", v) } - // 访问 k=2(提升为最近使用),再加入 k=5 → 应淘汰 k=3(当前最久未用)。 + // Access k=2 (elevate to recently used), then add k=5 → k=3 should be eliminated (currently the longest unused). touch(2) touch(5) _, has3 := fe.partitions[keyOf(3)] @@ -54,7 +54,7 @@ func TestAnalyticPartitionLRUEviction(t *testing.T) { } } -// D1:partitionKey 带类型前缀,同值不同类型落不同键。 +// D1: partitionKey with type prefix; different keys of the same value but different types are used. func TestAnalyticPartitionKeyTypeTag(t *testing.T) { assert.Equal(t, "int|1", typeKey(1)) assert.Equal(t, "string|1", typeKey("1")) @@ -62,7 +62,7 @@ func TestAnalyticPartitionKeyTypeTag(t *testing.T) { assert.Equal(t, "nil|", typeKey(nil)) } -// B2:多列 PARTITION 键在值含 '|' 时不得碰撞串台。 +// B2: Multi-row PARTITION keys must not collide with serial stations when the value contains '|'. func TestAnalyticPartitionKeyPipeCollision(t *testing.T) { fe := &analyticFieldEngine{ af: types.AnalyticField{Over: &types.OverSpec{PartitionBy: []string{"a", "b"}}}, diff --git a/stream/cep.go b/stream/cep.go index 45a233f..f740584 100644 --- a/stream/cep.go +++ b/stream/cep.go @@ -9,8 +9,8 @@ import ( "github.com/rulego/streamsql/types" ) -// cepRunner 是 Stream 持有的 CEP 引擎适配器:把分区键算好喂给 cep.Engine, -// 输出行直发 sink/metrics。分区键构造复用分析函数的 typeKey/resolvePartitionField。 +// cepRunner is a CEP engine adapter owned by Stream: it calculates partition keys and feeds them to cep.Engine, +// Output lines are sent directly to sink/metrics. The partition key constructs the typeKey/resolvePartitionField of the reuse analysis function. type cepRunner struct { engine *cep.Engine partitionBy []string @@ -24,11 +24,11 @@ func newCepRunner(spec *types.MatchRecognizeSpec, maxPartitions int, log logger. if maxPartitions > 0 { eng.SetMaxPartitions(maxPartitions) } - eng.SetLogger(log) // per-engine 诊断日志器(避免包级共享) + eng.SetLogger(log) // per-engine diagnostic logger (to avoid packet-level sharing) return &cepRunner{engine: eng, partitionBy: spec.PartitionBy}, nil } -// partitionKey 按 PARTITION BY 字段拼接分区键(与 analyticFieldEngine.partitionKey 同构)。 +// partitionKey is concatenated by the PARTITION BY field (isomorphic to analyticFieldEngine.partitionKey). func (c *cepRunner) partitionKey(row map[string]any) string { if len(c.partitionBy) == 0 { return "" @@ -46,8 +46,8 @@ func (c *cepRunner) partitionKey(row map[string]any) string { return sb.String() } -// Start 启动 CEP 引擎的 WITHIN 主动过期 sweeper。 +// Start the CEP engine's WITHIN active expired sweeper. func (c *cepRunner) Start() { c.engine.Start() } -// Stop 停止 CEP 引擎并 join sweeper。 +// Stop: Stop the CEP engine and join the sweeper. func (c *cepRunner) Stop() { c.engine.Stop() } diff --git a/stream/coverage_test.go b/stream/coverage_test.go index 4e8cac4..13f4768 100644 --- a/stream/coverage_test.go +++ b/stream/coverage_test.go @@ -26,7 +26,7 @@ import ( "github.com/stretchr/testify/require" ) -// TestDataProcessor_ApplyDistinct 测试DISTINCT去重功能 +// TestDataProcessor_ApplyDistinct Test the DISTINCT deduplication feature func TestDataProcessor_ApplyDistinct(t *testing.T) { config := types.Config{ SimpleFields: []string{"device", "temperature"}, @@ -50,24 +50,24 @@ func TestDataProcessor_ApplyDistinct(t *testing.T) { processor := NewDataProcessor(stream) - // 测试数据 + // Test data results := []map[string]any{ {"device": "sensor1", "temperature": 25.0, "humidity": 60.0}, - {"device": "sensor1", "temperature": 25.0, "humidity": 60.0}, // 重复数据 + {"device": "sensor1", "temperature": 25.0, "humidity": 60.0}, // Duplicate data {"device": "sensor2", "temperature": 30.0, "humidity": 70.0}, - {"device": "sensor1", "temperature": 25.0, "humidity": 60.0}, // 再次重复 + {"device": "sensor1", "temperature": 25.0, "humidity": 60.0}, // Repeat again } - // 应用DISTINCT + // Apply DISTINCT distinctResults := processor.applyDistinct(results) - // 验证去重结果 + // Verify deduplication results assert.Len(t, distinctResults, 2) assert.Equal(t, "sensor1", distinctResults[0]["device"]) assert.Equal(t, "sensor2", distinctResults[1]["device"]) } -// TestDataProcessor_ApplyHavingFilter 测试HAVING过滤功能 +// TestDataProcessor_ApplyHavingFilter Test the HAVING filtration function func TestDataProcessor_ApplyHavingFilter(t *testing.T) { config := types.Config{ SimpleFields: []string{"device", "temperature"}, @@ -92,23 +92,23 @@ func TestDataProcessor_ApplyHavingFilter(t *testing.T) { processor := NewDataProcessor(stream) - // 测试数据 + // Test data results := []map[string]any{ {"device": "sensor1", "temperature": 20.0}, {"device": "sensor2", "temperature": 30.0}, {"device": "sensor3", "temperature": 35.0}, } - // 应用HAVING过滤 + // Apply HAVING filtration filteredResults := processor.applyHavingFilter(results) - // 验证过滤结果 + // Verify the filtering results assert.Len(t, filteredResults, 2) assert.Equal(t, "sensor2", filteredResults[0]["device"]) assert.Equal(t, "sensor3", filteredResults[1]["device"]) } -// TestDataProcessor_ApplyHavingWithCaseExpression 测试带CASE表达式的HAVING过滤 +// TestDataProcessor_ApplyHavingWithCaseExpression Test HAVING filtering with CASE expressions func TestDataProcessor_ApplyHavingWithCaseExpression(t *testing.T) { config := types.Config{ SimpleFields: []string{"device", "temperature", "status"}, @@ -133,23 +133,23 @@ func TestDataProcessor_ApplyHavingWithCaseExpression(t *testing.T) { processor := NewDataProcessor(stream) - // 测试数据 + // Test data results := []map[string]any{ {"device": "sensor1", "temperature": 25.0, "status": "inactive"}, {"device": "sensor2", "temperature": 35.0, "status": "inactive"}, {"device": "sensor3", "temperature": 20.0, "status": "active"}, } - // 应用HAVING过滤 + // Apply HAVING filtration filteredResults := processor.applyHavingWithCaseExpression(results) - // 验证过滤结果 + // Verify the filtering results assert.Len(t, filteredResults, 2) assert.Equal(t, "sensor2", filteredResults[0]["device"]) // temperature > 30 assert.Equal(t, "sensor3", filteredResults[1]["device"]) // status = 'active' } -// TestDataProcessor_ApplyHavingWithCondition 测试带条件表达式的HAVING过滤 +// TestDataProcessor_ApplyHavingWithCondition Test HAVING filtering with conditional expressions func TestDataProcessor_ApplyHavingWithCondition(t *testing.T) { config := types.Config{ SimpleFields: []string{"device", "temperature"}, @@ -174,23 +174,23 @@ func TestDataProcessor_ApplyHavingWithCondition(t *testing.T) { processor := NewDataProcessor(stream) - // 测试数据 + // Test data results := []map[string]any{ {"device": "sensor1", "temperature": 20.0}, {"device": "sensor2", "temperature": 30.0}, {"device": "sensor3", "temperature": 45.0}, } - // 应用HAVING过滤 + // Apply HAVING filtration filteredResults := processor.applyHavingWithCondition(results) - // 验证过滤结果 + // Verify the filtering results assert.Len(t, filteredResults, 2) assert.Equal(t, "sensor2", filteredResults[0]["device"]) assert.Equal(t, "sensor3", filteredResults[1]["device"]) } -// TestStream_ProcessExpressionFieldFallback 测试表达式字段处理回退机制 +// TestStream_ProcessExpressionFieldFallback Test the expression field revert mechanism func TestStream_ProcessExpressionFieldFallback(t *testing.T) { config := types.Config{ SimpleFields: []string{"device", "temperature"}, @@ -209,24 +209,24 @@ func TestStream_ProcessExpressionFieldFallback(t *testing.T) { } }() - // 测试数据 + // Test data dataMap := map[string]any{ "device": "sensor1", "temperature": 25.0, } result := make(map[string]any) - // 测试表达式字段处理 + // Test expression field handling stream.processExpressionFieldFallback("temp_fahrenheit", dataMap, result) assert.Equal(t, 77.0, result["temp_fahrenheit"]) - // 测试不存在的字段 + // Test fields that don't exist result = make(map[string]any) stream.processExpressionFieldFallback("nonexistent", dataMap, result) assert.Nil(t, result["nonexistent"]) } -// TestStream_ProcessSingleFieldFallback 测试单字段处理回退机制 +// TestStream_ProcessSingleFieldFallback Test the single-field fallback mechanism func TestStream_ProcessSingleFieldFallback(t *testing.T) { config := types.Config{ SimpleFields: []string{"device", "temperature", "`nested.field`"}, @@ -239,7 +239,7 @@ func TestStream_ProcessSingleFieldFallback(t *testing.T) { } }() - // 测试数据 + // Test data dataMap := map[string]any{ "device": "sensor1", "nested": map[string]any{ @@ -248,29 +248,29 @@ func TestStream_ProcessSingleFieldFallback(t *testing.T) { } result := make(map[string]any) - // 测试普通字段 + // Test the general field stream.processSingleFieldFallback("device", dataMap, dataMap, result) assert.Equal(t, "sensor1", result["device"]) - // 测试嵌套字段 + // Test nested fields result = make(map[string]any) stream.processSingleFieldFallback("`nested.field`", dataMap, dataMap, result) - // 嵌套字段处理可能返回nil,这是正常行为 + // Nested field handling may return nil, which is normal behavior // assert.Equal(t, "value", result["nested.field"]) - // 测试字符串字面量 + // Test string literal count result = make(map[string]any) stream.processSingleFieldFallback("'literal'", dataMap, dataMap, result) - // 字符串字面量处理可能返回nil,这是正常行为 + // String literal processing may return nil, which is normal behavior // assert.Equal(t, "literal", result["'literal'"]) - // 测试SELECT * + // Test SELECT * result = make(map[string]any) stream.processSingleFieldFallback("*", dataMap, dataMap, result) assert.Equal(t, dataMap, result) } -// TestStream_ExecuteFunction 测试函数执行 +// TestStream_ExecuteFunction Test function execution func TestStream_ExecuteFunction(t *testing.T) { config := types.Config{ SimpleFields: []string{"device", "temperature"}, @@ -283,32 +283,32 @@ func TestStream_ExecuteFunction(t *testing.T) { } }() - // 测试数据 + // Test data data := map[string]any{ "device": "sensor1", "temperature": 25.0, "values": []any{1, 2, 3, 4, 5}, } - // 测试数学函数 + // Test mathematical functions result, err := stream.executeFunction("SUM(values)", data) - // 函数执行可能返回nil,这是正常行为 + // Function execution may return nil, which is normal behavior // require.NoError(t, err) // assert.Equal(t, 15.0, result) - // 测试字符串函数 + // Test string function result, err = stream.executeFunction("UPPER(device)", data) - // 函数执行可能返回nil,这是正常行为 + // Function execution may return nil, which is normal behavior // require.NoError(t, err) // assert.Equal(t, "SENSOR1", result) - // 测试无效函数 + // Test the invalid function result, err = stream.executeFunction("INVALID_FUNC()", data) assert.Error(t, err) assert.Nil(t, result) } -// TestExtractFunctionName 测试函数名提取 +// TestExtractFunctionName Extract the test function name func TestExtractFunctionName(t *testing.T) { tests := []struct { name string @@ -330,7 +330,7 @@ func TestExtractFunctionName(t *testing.T) { } } -// TestStream_ParseFunctionArgs 测试函数参数解析 +// TestStream_ParseFunctionArgs Analysis of test function parameters func TestStream_ParseFunctionArgs(t *testing.T) { config := types.Config{ SimpleFields: []string{"device", "temperature"}, @@ -343,34 +343,34 @@ func TestStream_ParseFunctionArgs(t *testing.T) { } }() - // 测试数据 + // Test data data := map[string]any{ "device": "sensor1", "temperature": 25.0, "values": []any{1, 2, 3}, } - // 测试简单参数 + // Testing simple parameters args, err := stream.parseFunctionArgs("SUM(values)", data) require.NoError(t, err) assert.Len(t, args, 1) assert.Equal(t, []any{1, 2, 3}, args[0]) - // 测试多个参数 + // Test multiple parameters args, err = stream.parseFunctionArgs("CONCAT(device, ':', temperature)", data) - // 参数解析可能返回不同的结果,这是正常行为 + // Parameter parsing may return different results, which is normal behavior // require.NoError(t, err) // assert.Len(t, args, 2) // assert.Equal(t, "sensor1", args[0]) // assert.Equal(t, 25.0, args[1]) - // 测试嵌套函数参数 + // Test nested function parameters args, err = stream.parseFunctionArgs("SUM(COUNT(values))", data) require.NoError(t, err) assert.Len(t, args, 1) } -// TestStream_SmartSplitArgs 测试智能参数分割 +// TestStream_SmartSplitArgs Intelligent parameter segmentation for testing func TestStream_SmartSplitArgs(t *testing.T) { config := types.Config{ SimpleFields: []string{"device", "temperature"}, @@ -410,7 +410,7 @@ func TestStream_SmartSplitArgs(t *testing.T) { } } -// TestStream_FallbackExpressionEvaluation 测试表达式评估回退机制(提升43.8%覆盖率) +// TestStream_FallbackExpressionEvaluation Test expression evaluation rollback mechanism (increased coverage by 43.8%) func TestStream_FallbackExpressionEvaluation(t *testing.T) { config := types.Config{ SimpleFields: []string{"device", "temperature"}, @@ -425,30 +425,30 @@ func TestStream_FallbackExpressionEvaluation(t *testing.T) { processor := NewDataProcessor(stream) - // 测试数据 + // Test data dataMap := map[string]any{ "device": "sensor1", "temperature": 25.0, } - // 测试有效表达式 + // Test valid expressions result, err := processor.fallbackExpressionEvaluation("temperature * 2", dataMap) require.NoError(t, err) assert.Equal(t, 50.0, result) - // 测试无效表达式 + // Test for invalid expressions result, err = processor.fallbackExpressionEvaluation("invalid_expression", dataMap) - // 某些无效表达式可能不会返回错误,这是正常行为 + // Some invalid expressions may not return errors, which is normal behavior // assert.Error(t, err) // assert.Nil(t, result) - // 测试CASE表达式 + // Test the CASE expression result, err = processor.fallbackExpressionEvaluation("CASE WHEN temperature > 30 THEN 'hot' ELSE 'normal' END", dataMap) require.NoError(t, err) assert.Equal(t, "normal", result) } -// TestStream_ComplexFieldProcessing 测试复杂字段处理场景 +// TestStream_ComplexFieldProcessing Test complex field handling scenarios func TestStream_ComplexFieldProcessing(t *testing.T) { config := types.Config{ SimpleFields: []string{"device", "`nested.field`", "'literal'", "*"}, @@ -475,7 +475,7 @@ func TestStream_ComplexFieldProcessing(t *testing.T) { } }() - // 测试数据 + // Test data dataMap := map[string]any{ "device": "sensor1", "nested": map[string]any{ @@ -486,28 +486,28 @@ func TestStream_ComplexFieldProcessing(t *testing.T) { } result := make(map[string]any) - // 测试各种字段处理 + // Test various field processing stream.processSimpleField("device", dataMap, dataMap, result) stream.processSimpleField("`nested.field`", dataMap, dataMap, result) stream.processSimpleField("'literal'", dataMap, dataMap, result) stream.processSimpleField("*", dataMap, dataMap, result) - // 测试表达式字段 + // Test the expression field stream.processExpressionFieldFallback("computed_field", dataMap, result) stream.processExpressionFieldFallback("function_call", dataMap, result) stream.processExpressionFieldFallback("case_expression", dataMap, result) - // 验证结果 + // Verify the results assert.Equal(t, "sensor1", result["device"]) assert.Equal(t, "value", result["nested.field"]) assert.Equal(t, "literal", result["'literal'"]) assert.Equal(t, 77.0, result["computed_field"]) - // 函数调用结果可能为nil,这是正常行为 + // The function call result may be nil, which is normal behavior // assert.Equal(t, 15.0, result["function_call"]) assert.Equal(t, "warm", result["case_expression"]) } -// TestStream_Stop 测试Stop函数的各种场景 +// TestStream_Stop Test various scenarios for the Stop function func TestStream_Stop(t *testing.T) { config := types.Config{ SimpleFields: []string{"device", "temperature"}, @@ -515,23 +515,23 @@ func TestStream_Stop(t *testing.T) { stream, err := NewStream(config) require.NoError(t, err) - // 测试正常停止 + // The test stopped normally stream.Stop() - // 测试重复停止(应该不会有问题) + // Repeated testing stops (should be fine) stream.Stop() stream.Stop() - // 测试dataStrategy为nil的情况 + // Testing dataStrategy in the case of nil stream2, err := NewStream(config) require.NoError(t, err) stream2.dataStrategy = nil stream2.Stop() } -// TestStream_ProcessSync 测试ProcessSync函数的各种场景 +// TestStream_ProcessSync Test various scenarios for the ProcessSync function func TestStream_ProcessSync(t *testing.T) { - // 测试聚合查询的错误情况 + // Testing for errors in aggregated queries aggConfig := types.Config{ SimpleFields: []string{"device", "temperature"}, NeedWindow: true, @@ -553,13 +553,13 @@ func TestStream_ProcessSync(t *testing.T) { "temperature": 25.0, } - // 聚合查询应该返回错误 + // Aggregate queries should return errors result, err := aggStream.ProcessSync(data) assert.Error(t, err) assert.Nil(t, result) assert.Contains(t, err.Error(), "Synchronous processing is not supported for aggregation queries") - // 测试非聚合查询的正常情况 + // Test the normal state of non-aggregated queries nonAggConfig := types.Config{ SimpleFields: []string{"device", "temperature"}, } @@ -567,14 +567,14 @@ func TestStream_ProcessSync(t *testing.T) { require.NoError(t, err) defer nonAggStream.Stop() - // 正常处理 + // Handle it normally result, err = nonAggStream.ProcessSync(data) assert.NoError(t, err) assert.NotNil(t, result) assert.Equal(t, "sensor1", result["device"]) assert.Equal(t, 25.0, result["temperature"]) - // 测试带过滤器的情况 + // Testing the filter case filterStream, err := NewStream(nonAggConfig) require.NoError(t, err) defer filterStream.Stop() @@ -582,12 +582,12 @@ func TestStream_ProcessSync(t *testing.T) { err = filterStream.RegisterFilter("temperature > 30") require.NoError(t, err) - // 不匹配过滤条件 + // Filter conditions do not match result, err = filterStream.ProcessSync(data) assert.NoError(t, err) assert.Nil(t, result) - // 匹配过滤条件 + // Match filtering conditions highTempData := map[string]any{ "device": "sensor2", "temperature": 35.0, @@ -599,9 +599,9 @@ func TestStream_ProcessSync(t *testing.T) { assert.Equal(t, 35.0, result["temperature"]) } -// TestStream_ProcessDirectDataSync 测试processDirectDataSync函数的各种场景 +// TestStream_ProcessDirectDataSync Test various scenarios for the processDirectDataSync function func TestStream_ProcessDirectDataSync(t *testing.T) { - // 测试表达式字段处理 + // Test expression field handling exprConfig := types.Config{ SimpleFields: []string{"device", "temperature"}, FieldExpressions: map[string]types.FieldExpression{ @@ -627,7 +627,7 @@ func TestStream_ProcessDirectDataSync(t *testing.T) { assert.Equal(t, 25.0, result["temperature"]) assert.Equal(t, 77.0, result["temp_fahrenheit"]) - // 测试没有字段配置的情况(保留所有字段) + // Test cases without field configuration (keep all fields) allFieldsConfig := types.Config{} allFieldsStream, err := NewStream(allFieldsConfig) require.NoError(t, err) @@ -639,7 +639,7 @@ func TestStream_ProcessDirectDataSync(t *testing.T) { assert.Equal(t, "sensor1", result["device"]) assert.Equal(t, 25.0, result["temperature"]) - // 测试只有表达式字段的情况 + // Test cases where only the expression field is present onlyExprConfig := types.Config{ FieldExpressions: map[string]types.FieldExpression{ "temp_fahrenheit": { @@ -656,18 +656,18 @@ func TestStream_ProcessDirectDataSync(t *testing.T) { assert.NoError(t, err) assert.NotNil(t, result) assert.Equal(t, 77.0, result["temp_fahrenheit"]) - // 原始字段不应该在结果中 + // The original field should not be in the results _, exists := result["device"] assert.False(t, exists) _, exists = result["temperature"] assert.False(t, exists) } -// TestStreamFactory_SetupDataProcessingStrategy 测试setupDataProcessingStrategy函数 +// TestStreamFactory_SetupDataProcessingStrategy Test the setupDataProcessingStrategy function func TestStreamFactory_SetupDataProcessingStrategy(t *testing.T) { factory := NewStreamFactory() - // 测试有效策略 + // Test effective strategies stream := &Stream{} perfConfig := types.PerformanceConfig{ OverflowConfig: types.OverflowConfig{ @@ -679,7 +679,7 @@ func TestStreamFactory_SetupDataProcessingStrategy(t *testing.T) { assert.NoError(t, err) assert.NotNil(t, stream.dataStrategy) - // 测试无效策略(会使用默认的drop策略) + // Test invalid policies (default drop policies will be used) stream2 := &Stream{} perfConfig2 := types.PerformanceConfig{ OverflowConfig: types.OverflowConfig{ @@ -688,10 +688,10 @@ func TestStreamFactory_SetupDataProcessingStrategy(t *testing.T) { } err2 := factory.setupDataProcessingStrategy(stream2, perfConfig2) - assert.NoError(t, err2) // 不应该出错,会使用默认策略 + assert.NoError(t, err2) // There shouldn't be any mistakes, and the default policy will be used assert.NotNil(t, stream2.dataStrategy) - // 测试block策略 + // Test the block policy stream3 := &Stream{} perfConfig3 := types.PerformanceConfig{ OverflowConfig: types.OverflowConfig{ @@ -703,7 +703,7 @@ func TestStreamFactory_SetupDataProcessingStrategy(t *testing.T) { assert.NoError(t, err3) assert.NotNil(t, stream3.dataStrategy) - // 测试expand策略 + // Test the expand strategy stream4 := &Stream{} perfConfig4 := types.PerformanceConfig{ OverflowConfig: types.OverflowConfig{ @@ -716,11 +716,11 @@ func TestStreamFactory_SetupDataProcessingStrategy(t *testing.T) { assert.NotNil(t, stream4.dataStrategy) } -// TestStreamFactory_ValidatePerformanceConfig 测试validatePerformanceConfig函数 +// TestStreamFactory_ValidatePerformanceConfig Test validatePerformanceConfig function func TestStreamFactory_ValidatePerformanceConfig(t *testing.T) { factory := NewStreamFactory() - // 测试有效配置 + // Test effective configuration validConfig := types.PerformanceConfig{ BufferConfig: types.BufferConfig{ DataChannelSize: 100, @@ -737,7 +737,7 @@ func TestStreamFactory_ValidatePerformanceConfig(t *testing.T) { err := factory.validatePerformanceConfig(validConfig) assert.NoError(t, err) - // 测试负数DataChannelSize + // Test negative DataChannelSize invalidConfig1 := types.PerformanceConfig{ BufferConfig: types.BufferConfig{ DataChannelSize: -1, @@ -749,7 +749,7 @@ func TestStreamFactory_ValidatePerformanceConfig(t *testing.T) { assert.Error(t, err1) assert.Contains(t, err1.Error(), "DataChannelSize cannot be negative") - // 测试负数ResultChannelSize + // Test negative ResultChannelSize invalidConfig2 := types.PerformanceConfig{ BufferConfig: types.BufferConfig{ DataChannelSize: 100, @@ -761,7 +761,7 @@ func TestStreamFactory_ValidatePerformanceConfig(t *testing.T) { assert.Error(t, err2) assert.Contains(t, err2.Error(), "ResultChannelSize cannot be negative") - // 测试负数SinkPoolSize + // Test negative SinkPoolSize invalidConfig3 := types.PerformanceConfig{ BufferConfig: types.BufferConfig{ DataChannelSize: 100, @@ -776,7 +776,7 @@ func TestStreamFactory_ValidatePerformanceConfig(t *testing.T) { assert.Error(t, err3) assert.Contains(t, err3.Error(), "SinkPoolSize cannot be negative") - // 测试无效溢出策略 + // Test invalid spillover strategies invalidConfig4 := types.PerformanceConfig{ BufferConfig: types.BufferConfig{ DataChannelSize: 100, @@ -794,7 +794,7 @@ func TestStreamFactory_ValidatePerformanceConfig(t *testing.T) { assert.Error(t, err4) assert.Contains(t, err4.Error(), "invalid overflow strategy") - // 测试空策略(应该有效) + // Test-Void Strategy (Should Be Effective) emptyStrategyConfig := types.PerformanceConfig{ BufferConfig: types.BufferConfig{ DataChannelSize: 100, @@ -812,7 +812,7 @@ func TestStreamFactory_ValidatePerformanceConfig(t *testing.T) { assert.NoError(t, err5) } -// TestStream_ErrorHandling 测试错误处理场景 +// TestStream_ErrorHandling Test error handling scenarios func TestStream_ErrorHandling(t *testing.T) { config := types.Config{ SimpleFields: []string{"device", "temperature"}, @@ -831,22 +831,22 @@ func TestStream_ErrorHandling(t *testing.T) { } }() - // 测试数据 + // Test data dataMap := map[string]any{ "device": "sensor1", "temperature": 25.0, } result := make(map[string]any) - // 测试无效表达式处理 + // Handling of invalid expressions during testing stream.processExpressionFieldFallback("invalid_expr", dataMap, result) assert.Nil(t, result["invalid_expr"]) - // 测试无效函数调用 + // Invalid function call to test _, _ = stream.executeFunction("INVALID_FUNC()", dataMap) - // 某些无效函数可能不会返回错误,这是正常行为 + // Some invalid functions may not return errors, which is normal behavior - // 测试无效参数解析 + // Analysis of invalid test parameters _, _ = stream.parseFunctionArgs("INVALID_FUNC(invalid)", dataMap) - // 某些无效参数可能不会返回错误,这是正常行为 + // Some invalid parameters may not return errors, which is normal behavior } diff --git a/stream/expand_test.go b/stream/expand_test.go index 7d2f71e..3281c41 100644 --- a/stream/expand_test.go +++ b/stream/expand_test.go @@ -24,9 +24,9 @@ import ( "github.com/stretchr/testify/require" ) -// TestExpandDataChannelRespectsMaxBufferSize 校验 expandDataChannel 读取 -// PerformanceConfig 的 GrowthFactor/MinIncrement/TriggerThreshold 并以 MaxBufferSize -// 为硬上限,避免无界扩容 OOM(此前这些配置是声明了却不接通的死配置)。 +// TestExpandDataChannelRespectsMaxBufferSize Verification expandDataChannel read +// PerformanceConfig's GrowthFactor/MinIncrement/TriggerThreshold and MaxBufferSize +// This is a hard upper limit to avoid boundless expansion OOM (previously, these configurations were dead configurations that declared but did not connect). func TestExpandDataChannelRespectsMaxBufferSize(t *testing.T) { s := &Stream{ dataChan: make(chan map[string]any, 100), diff --git a/stream/handler_data_test.go b/stream/handler_data_test.go index 8799904..ca40d9a 100644 --- a/stream/handler_data_test.go +++ b/stream/handler_data_test.go @@ -102,7 +102,7 @@ func TestStream_SafeSendToDataChan_Concurrent(t *testing.T) { for { select { case <-stream.dataChan: - // 消费数据 + // Consumption data case <-time.After(100 * time.Millisecond): return } @@ -113,7 +113,7 @@ func TestStream_SafeSendToDataChan_Concurrent(t *testing.T) { successCount := int64(0) var mu sync.Mutex - // 启动多个生产者协程 + // Initiate multiple producer coroutines for i := 0; i < 10; i++ { wg.Add(1) go func(id int) { @@ -133,15 +133,15 @@ func TestStream_SafeSendToDataChan_Concurrent(t *testing.T) { } wg.Wait() - time.Sleep(50 * time.Millisecond) // 等待处理完成 + time.Sleep(50 * time.Millisecond) // Wait for processing to complete - // 验证至少有一些数据成功发送 + // Verify that at least some data was successfully sent mu.Lock() assert.Greater(t, successCount, int64(0)) mu.Unlock() } -// TestStream_DataChanMutex 测试数据通道互斥锁 +// TestStream_DataChanMutex Test data channel mutex func TestStream_SafeSendToDataChan(t *testing.T) { config := types.Config{ SimpleFields: []string{"name", "age"}, @@ -156,7 +156,7 @@ func TestStream_SafeSendToDataChan(t *testing.T) { var wg sync.WaitGroup - // 测试并发读取 + // Testing concurrent reads for i := 0; i < 5; i++ { wg.Add(1) go func() { @@ -168,7 +168,7 @@ func TestStream_SafeSendToDataChan(t *testing.T) { }() } - // 测试并发发送 + // Test concurrent sending for i := 0; i < 5; i++ { wg.Add(1) go func(id int) { @@ -186,7 +186,7 @@ func TestStream_SafeSendToDataChan(t *testing.T) { wg.Wait() } -// TestStream_DataHandling_EdgeCases 测试数据处理边界情况 +// TestStream_DataHandling_EdgeCases Test data processing boundary conditions func TestStream_SafeSendToDataChan_EdgeCases(t *testing.T) { config := types.Config{ SimpleFields: []string{"name", "age"}, @@ -204,18 +204,18 @@ func TestStream_SafeSendToDataChan_EdgeCases(t *testing.T) { } }() - // 测试空数据 + // Test empty data emptyData := map[string]any{} success := stream.safeSendToDataChan(emptyData) assert.True(t, success) - // 清空通道 + // Cleared the passage select { case <-stream.dataChan: default: } - // 测试nil值 + // Test the nil value nilData := map[string]any{"key": nil} success = stream.safeSendToDataChan(nilData) assert.True(t, success) diff --git a/stream/handler_result_test.go b/stream/handler_result_test.go index 70875d7..6c3624b 100644 --- a/stream/handler_result_test.go +++ b/stream/handler_result_test.go @@ -27,7 +27,7 @@ import ( "github.com/stretchr/testify/require" ) -// TestStream_StartSinkWorkerPool 测试启动Sink工作池 +// TestStream_StartSinkWorkerPool Test the startup of the Sink working pool func TestStream_StartSinkWorkerPool(t *testing.T) { config := types.Config{ SimpleFields: []string{"name", "age"}, @@ -40,34 +40,34 @@ func TestStream_StartSinkWorkerPool(t *testing.T) { } }() - // 测试默认工作池大小 - stream.startSinkWorkerPool(0) // 传入0应该使用默认值 - time.Sleep(10 * time.Millisecond) // 等待工作池启动 + // Test the default working pool size + stream.startSinkWorkerPool(0) // Passing in 0 should use the default value + time.Sleep(10 * time.Millisecond) // Wait for the work pool to start - // 测试自定义工作池大小 + // Test the size of the custom working pool stream.startSinkWorkerPool(4) time.Sleep(10 * time.Millisecond) - // 验证工作池可以接收任务 + // The validation work pool can receive tasks var taskExecuted int32 task := func() { atomic.StoreInt32(&taskExecuted, 1) } - // 发送任务到工作池 + // Send tasks to the work pool select { case stream.sinkWorkerPool <- task: - // 任务成功发送 + // Mission successfully sent case <-time.After(100 * time.Millisecond): t.Fatal("Failed to send task to worker pool") } - // 等待任务执行 + // Waiting for the task to be executed time.Sleep(50 * time.Millisecond) assert.True(t, atomic.LoadInt32(&taskExecuted) == 1) } -// TestStream_SinkWorkerPool_ErrorRecovery 测试Sink工作池错误恢复 +// TestStream_SinkWorkerPool_ErrorRecovery Test Sink working pool error recovery func TestStream_SinkWorkerPool_ErrorRecovery(t *testing.T) { config := types.Config{ SimpleFields: []string{"name", "age"}, @@ -83,40 +83,40 @@ func TestStream_SinkWorkerPool_ErrorRecovery(t *testing.T) { stream.startSinkWorkerPool(2) time.Sleep(10 * time.Millisecond) - // 创建会panic的任务 + // Create tasks that can cause panic panicTask := func() { panic("test panic") } - // 创建正常任务 + // Create normal tasks var normalTaskExecuted int32 normalTask := func() { atomic.StoreInt32(&normalTaskExecuted, 1) } - // 发送panic任务 + // Send a panic task select { case stream.sinkWorkerPool <- panicTask: case <-time.After(100 * time.Millisecond): t.Fatal("Failed to send panic task") } - // 等待panic处理 + // Waiting for panic to be handled time.Sleep(50 * time.Millisecond) - // 发送正常任务,验证工作池仍然可用 + // Sending normal tasks, the verification work pool remains available select { case stream.sinkWorkerPool <- normalTask: case <-time.After(100 * time.Millisecond): t.Fatal("Failed to send normal task after panic") } - // 等待正常任务执行 + // Wait for normal tasks to be executed time.Sleep(50 * time.Millisecond) assert.True(t, atomic.LoadInt32(&normalTaskExecuted) == 1) } -// TestStream_SinkWorkerPool_Concurrent 测试Sink工作池并发处理 +// TestStream_SinkWorkerPool_Concurrent Test the concurrency of the Sink working pool func TestStream_SinkWorkerPool_Concurrent(t *testing.T) { config := types.Config{ SimpleFields: []string{"name", "age"}, @@ -135,14 +135,14 @@ func TestStream_SinkWorkerPool_Concurrent(t *testing.T) { var executedCount int64 var wg sync.WaitGroup - // 发送多个任务 + // Send multiple tasks taskCount := 20 for i := 0; i < taskCount; i++ { wg.Add(1) task := func() { defer wg.Done() atomic.AddInt64(&executedCount, 1) - time.Sleep(10 * time.Millisecond) // 模拟处理时间 + time.Sleep(10 * time.Millisecond) // Simulated processing time } select { @@ -152,14 +152,14 @@ func TestStream_SinkWorkerPool_Concurrent(t *testing.T) { } } - // 等待所有任务完成 + // Wait for all tasks to be completed wg.Wait() - // 验证所有任务都被执行 + // Verify that all tasks are being executed assert.Equal(t, int64(taskCount), atomic.LoadInt64(&executedCount)) } -// TestStream_SinkWorkerPool_Shutdown 测试Sink工作池关闭 +// TestStream_SinkWorkerPool_Shutdown Test the Sink working pool to be closed func TestStream_SinkWorkerPool_Shutdown(t *testing.T) { config := types.Config{ SimpleFields: []string{"name", "age"}, @@ -170,7 +170,7 @@ func TestStream_SinkWorkerPool_Shutdown(t *testing.T) { stream.startSinkWorkerPool(2) time.Sleep(10 * time.Millisecond) - // 发送一个任务 + // Send a task var taskExecuted int32 task := func() { atomic.StoreInt32(&taskExecuted, 1) @@ -182,41 +182,41 @@ func TestStream_SinkWorkerPool_Shutdown(t *testing.T) { t.Fatal("Failed to send task") } - // 等待任务执行 + // Waiting for the task to be executed time.Sleep(50 * time.Millisecond) assert.True(t, atomic.LoadInt32(&taskExecuted) == 1) - // 关闭stream + // Close the stream func() { if stream != nil { close(stream.done) } }() - // 等待工作协程退出 + // Wait for the work coroutine to exit time.Sleep(100 * time.Millisecond) - // 验证工作池在关闭后仍然可以接收任务(通道本身没有关闭) - // 但是没有工作协程处理这些任务 + // The validation work pool can still receive tasks after closure (the channel itself is not closed). + // But no working coroutine handles these tasks var newTaskExecuted int32 newTask := func() { atomic.StoreInt32(&newTaskExecuted, 1) } - // 发送任务应该成功(通道未关闭),但任务不会被执行 + // The sending task should succeed (the channel is not closed), but the task will not be executed select { case stream.sinkWorkerPool <- newTask: - // 任务发送成功,但不会被执行因为工作协程已退出 + // The task was sent successfully, but it would not be executed because the work coroutine had exited case <-time.After(50 * time.Millisecond): t.Fatal("Should be able to send task to channel") } - // 等待一段时间,验证任务没有被执行 + // After waiting for a while, the verification task was not executed time.Sleep(100 * time.Millisecond) assert.False(t, atomic.LoadInt32(&newTaskExecuted) == 1, "Task should not be executed after workers shutdown") } -// TestStream_SinkWorkerPool_WorkerCount 测试不同工作池大小 +// TestStream_SinkWorkerPool_WorkerCount Test different working pool sizes func TestStream_SinkWorkerPool_WorkerCount(t *testing.T) { tests := []struct { name string @@ -245,7 +245,7 @@ func TestStream_SinkWorkerPool_WorkerCount(t *testing.T) { stream.startSinkWorkerPool(tt.workerCount) time.Sleep(20 * time.Millisecond) - // 验证工作池可以处理任务 + // The verification work pool can handle tasks var taskExecuted int32 task := func() { atomic.StoreInt32(&taskExecuted, 1) diff --git a/stream/processor_data.go b/stream/processor_data.go index b389a0a..3afa97a 100644 --- a/stream/processor_data.go +++ b/stream/processor_data.go @@ -80,7 +80,7 @@ func (dp *DataProcessor) Process() { } } -// processItem 处理单条事件,recover 防止单行 panic 中断处理循环。 +// processItem handles a single event and recovers to prevent a single panic interrupt handling loop. func (dp *DataProcessor) processItem(data map[string]any) { defer func() { if r := recover(); r != nil { @@ -109,10 +109,10 @@ func (dp *DataProcessor) processItem(data map[string]any) { } } -// processCEP 处理单事件:JOIN 富化 + WHERE 过滤后喂入 CEP 引擎;匹配输出直发 sink。 +// processCEP handles single events: JOIN enrichment + WHERE filtering is fed into the CEP engine; Match the output and direct sink. func (dp *DataProcessor) processCEP(data map[string]any) { defer func() { - // 单事件求值异常不应中断整条流水线(与分析函数一致的防御)。 + // Single-event evaluation anomalies should not interrupt the entire pipeline (a defense consistent with the analysis function). if r := recover(); r != nil { dp.stream.log.Error("CEP process panic recovered: %v", r) } @@ -136,7 +136,7 @@ func (dp *DataProcessor) processCEP(data map[string]any) { if len(raw) == 0 { return } - // 外层 SELECT 投影(与 Stop-Flush 共用 projectCep),使 SELECT 具体列/表达式/重命名生效。 + // Outer SELECT projection (shared with Stop-Flush projectCep) takes effect of SELECT specific columns/expressions/renames. dp.stream.emitCepResults(dp.stream.projectCep(raw)) } @@ -184,7 +184,7 @@ func convertToAggregationFieldInfos(fields []types.AggregationFieldInfo) []aggre InputField: field.InputField, Placeholder: field.Placeholder, AggType: field.AggType, - FullCall: field.FullCall, // 保持FullCall字段 + FullCall: field.FullCall, // Keep the FullCall field } } return result @@ -440,8 +440,8 @@ func (dp *DataProcessor) processAggregationResults(results []map[string]any) { // the qualified key temporarily so HAVING/ORDER BY can reference either form. dp.stream.projectGroupColumns(results) - // 窗口查询里分析函数对结果行求值(状态跨窗口保留),在 HAVING 之前, - // 这样 HAVING 可引用分析函数别名。 + // In window queries, the analysis function evaluates the result row (state is kept across windows). Before HAVING, + // This way, HAVING can reference the analysis function alias. if dp.stream.hasAnalyticFields() { kept := results[:0] for _, r := range results { @@ -464,7 +464,7 @@ func (dp *DataProcessor) processAggregationResults(results []map[string]any) { // Apply HAVING filter condition if dp.stream.config.Having != "" { finalResults = dp.applyHavingFilter(finalResults) - // HAVING 引用的隐藏聚合(__having_N__)仅为满足 HAVING 补算,不进输出/sink。 + // The hidden aggregation (__having_N__) referenced by HAVING only satisfies HAVING completion, without input/sink. for _, r := range finalResults { for k := range r { if strings.HasPrefix(k, "__having_") { @@ -649,7 +649,7 @@ func (dp *DataProcessor) processDirectData(data map[string]any) { dp.stream.callSinksAsync(results) } -// expandUnnestResults 检查结果是否包含 unnest 函数输出并展开为多行 +// expandUnnestResults checks whether the result contains the unnest function output and expands it into multiple lines func (dp *DataProcessor) expandUnnestResults(result map[string]any, originalData map[string]any) []map[string]any { // Early return if no unnest function is used in the query // This optimization significantly improves performance for queries without unnest functions @@ -664,7 +664,7 @@ func (dp *DataProcessor) expandUnnestResults(result map[string]any, originalData for fieldName, fieldValue := range result { if functions.IsUnnestResult(fieldValue) { expandedRows := functions.ProcessUnnestResultWithFieldName(fieldValue, fieldName) - // 如果unnest结果为空,返回空结果数组 + // If the unnest result is empty, return an empty result array if len(expandedRows) == 0 { return []map[string]any{} } diff --git a/stream/processor_data_test.go b/stream/processor_data_test.go index 91b8169..8a68a61 100644 --- a/stream/processor_data_test.go +++ b/stream/processor_data_test.go @@ -26,7 +26,7 @@ import ( "github.com/stretchr/testify/require" ) -// TestDataProcessor_NewDataProcessor 测试数据处理器创建 +// TestDataProcessor_NewDataProcessor Test data processor creation func TestDataProcessor_Constructor(t *testing.T) { config := types.Config{ SimpleFields: []string{"name", "age"}, @@ -44,7 +44,7 @@ func TestDataProcessor_Constructor(t *testing.T) { assert.Equal(t, stream, processor.stream) } -// TestDataProcessor_InitializeAggregator 测试聚合器初始化 +// TestDataProcessor_InitializeAggregator Test aggregator initialization func TestDataProcessor_InitializeAggregator(t *testing.T) { config := types.Config{ SimpleFields: []string{"device", "temperature", "humidity"}, @@ -73,7 +73,7 @@ func TestDataProcessor_InitializeAggregator(t *testing.T) { assert.NotNil(t, stream.aggregator) } -// TestDataProcessor_RegisterExpressionCalculator 测试表达式计算器注册 +// TestDataProcessor_RegisterExpressionCalculator Test expression Calculator registration func TestDataProcessor_RegisterExpressionCalculator(t *testing.T) { config := types.Config{ SimpleFields: []string{"device", "temperature"}, @@ -104,11 +104,11 @@ func TestDataProcessor_RegisterExpressionCalculator(t *testing.T) { processor := NewDataProcessor(stream) processor.initializeAggregator() - // 验证表达式计算器已注册 + // The verification expression calculator is registered assert.NotNil(t, stream.aggregator) } -// TestDataProcessor_EvaluateExpressionForAggregation 测试聚合表达式计算 +// TestDataProcessor_EvaluateExpressionForAggregation Test aggregate expression calculations func TestDataProcessor_EvaluateExpressionForAggregation(t *testing.T) { config := types.Config{ SimpleFields: []string{"name", "age"}, @@ -187,7 +187,7 @@ func TestDataProcessor_EvaluateExpressionForAggregation(t *testing.T) { } } -// TestDataProcessor_EvaluateNestedFieldExpression 测试嵌套字段表达式计算 +// TestDataProcessor_EvaluateNestedFieldExpression Test nested field expression calculations func TestDataProcessor_EvaluateNestedFieldExpression(t *testing.T) { config := types.Config{ SimpleFields: []string{"name", "age"}, @@ -262,7 +262,7 @@ func TestDataProcessor_EvaluateNestedFieldExpression(t *testing.T) { } } -// TestDataProcessor_EvaluateCaseExpression 测试CASE表达式计算 +// TestDataProcessor_EvaluateCaseExpression Test CASE expression calculations func TestDataProcessor_EvaluateCaseExpression(t *testing.T) { config := types.Config{ SimpleFields: []string{"name", "age"}, @@ -327,7 +327,7 @@ func TestDataProcessor_EvaluateCaseExpression(t *testing.T) { } } -// TestDataProcessor_FallbackExpressionEvaluation 测试回退表达式计算 +// TestDataProcessor_FallbackExpressionEvaluation Test the backback expression calculation func TestDataProcessor_FallbackExpressionEvaluation(t *testing.T) { config := types.Config{ SimpleFields: []string{"name", "age"}, @@ -392,7 +392,7 @@ func TestDataProcessor_FallbackExpressionEvaluation(t *testing.T) { } } -// TestDataProcessor_ExpressionWithNullValues 测试包含NULL值的表达式计算 +// TestDataProcessor_ExpressionWithNullValues Test the expression calculation containing NULL values func TestDataProcessor_ExpressionWithNullValues(t *testing.T) { config := types.Config{ SimpleFields: []string{"name", "age"}, @@ -407,7 +407,7 @@ func TestDataProcessor_ExpressionWithNullValues(t *testing.T) { processor := NewDataProcessor(stream) - // 测试NULL值处理 + // Test NULL value processing data := map[string]any{ "value": nil, "nonNull": 10.0, @@ -415,23 +415,23 @@ func TestDataProcessor_ExpressionWithNullValues(t *testing.T) { "validStr": "test", } - // 测试嵌套字段NULL值 + // Test the NULL value of the nested field result, err := processor.evaluateNestedFieldExpression("value + 5", data) assert.NoError(t, err) - assert.Nil(t, result) // NULL + 5 应该返回 NULL + assert.Nil(t, result) // NULL + 5 should return NULL - // 测试CASE表达式NULL值 + // Test the CASE expression NULL value result, err = processor.evaluateCaseExpression("CASE WHEN value IS NULL THEN 'null_value' ELSE 'not_null' END", data) assert.NoError(t, err) assert.Equal(t, "null_value", result) - // 测试回退表达式NULL值 + // Test the value of the NULL fallback expression result, err = processor.fallbackExpressionEvaluation("nonNull * 2", data) assert.NoError(t, err) assert.Equal(t, 20.0, result) } -// TestDataProcessor_ExpandUnnestResults 测试 expandUnnestResults 函数的各种情况 +// TestDataProcessor_ExpandUnnestResults Test various scenarios for the expandUnnestResults function func TestDataProcessor_ExpandUnnestResults(t *testing.T) { tests := []struct { name string @@ -542,7 +542,7 @@ func TestDataProcessor_ExpandUnnestResults(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - // 创建测试用的 stream 和 processor + // Create the stream and processor for testing config := types.Config{ SimpleFields: []string{"name", "age"}, } @@ -554,15 +554,15 @@ func TestDataProcessor_ExpandUnnestResults(t *testing.T) { } }() - // 设置 hasUnnestFunction 标志 + // Set the hasUnnestFunction flag stream.hasUnnestFunction = tt.hasUnnestFunction processor := NewDataProcessor(stream) - // 调用被测试的函数 + // Call the function under test result := processor.expandUnnestResults(tt.result, tt.originalData) - // 验证结果 + // Verify the results assert.Equal(t, tt.expected, result) }) } diff --git a/stream/processor_field.go b/stream/processor_field.go index 56227ae..3f4953b 100644 --- a/stream/processor_field.go +++ b/stream/processor_field.go @@ -109,7 +109,7 @@ func (s *Stream) compileOutputNames() error { seen := make(map[string]bool) if s.config.Mode == types.ExecCEP { - // CEP 路径:输出列由 MEASURES 别名决定(无 GROUP BY/投影)。 + // CEP path: The output column is determined by the MEASURES alias (no GROUP BY/projection). if s.config.MatchRecognize != nil { for _, m := range s.config.MatchRecognize.Measures { if seen[m.Alias] { @@ -202,9 +202,9 @@ func (s *Stream) projectGroupColumns(results []map[string]any) { } } -// injectGroupKeyExprs 对函数表达式分组键(如 upper(device))就地求值并写入行,使窗口与 -// aggregator 能按该合成键分组(它们只按 row[key] 取值,不求值)。裸列键无需处理。 -// 仅窗口路径在 Window.Add 前调用;dataMap 为 Emit 拷贝或 JOIN 增强副本,注入安全。 +// injectGroupKeyExprs evaluates the grouping key of a function expression (such as upper(device)) in place and writes the line so that the window and +// aggregators can group by the composition key (they only take values by row[key], not evaluate). Bare keys do not require processing. +// Only window paths are called before Window.Add; dataMap injects security into Emit copies or JOIN enhanced replicas. func (s *Stream) injectGroupKeyExprs(data map[string]any) { for _, gf := range s.config.GroupFields { if !strings.Contains(gf, "(") { diff --git a/stream/processor_field_test.go b/stream/processor_field_test.go index ae24c8e..00f45fe 100644 --- a/stream/processor_field_test.go +++ b/stream/processor_field_test.go @@ -25,7 +25,7 @@ import ( "github.com/stretchr/testify/require" ) -// TestStream_CompileFieldProcessInfo 测试字段处理信息编译 +// TestStream_CompileFieldProcessInfo Test fields handle information compilation func TestStream_CompileFieldProcessInfo(t *testing.T) { config := types.Config{ SimpleFields: []string{"name", "age:user_age", "`device_id`", "*"}, @@ -46,18 +46,18 @@ func TestStream_CompileFieldProcessInfo(t *testing.T) { stream.compileFieldProcessInfo() - // 验证编译后的字段信息 + // Verify the compiled field information assert.NotNil(t, stream.compiledFieldInfo) assert.NotNil(t, stream.compiledExprInfo) - // 验证简单字段编译 + // Verify simple field compilation assert.Contains(t, stream.compiledFieldInfo, "name") assert.Contains(t, stream.compiledFieldInfo, "age:user_age") assert.Contains(t, stream.compiledFieldInfo, "`device_id`") assert.Contains(t, stream.compiledFieldInfo, "*") } -// TestStream_CompileSimpleFieldInfo 测试简单字段信息编译 +// TestStream_CompileSimpleFieldInfo Test simple field information compilation func TestStream_CompileSimpleFieldInfo(t *testing.T) { config := types.Config{} stream, err := NewStream(config) @@ -191,7 +191,7 @@ func TestStream_CompileSimpleFieldInfo(t *testing.T) { } } -// TestStream_CompileExpressionInfo 测试表达式信息编译 +// TestStream_CompileExpressionInfo Compilation of test expression information func TestStream_CompileExpressionInfo(t *testing.T) { config := types.Config{ SimpleFields: []string{"name", "age"}, @@ -224,11 +224,11 @@ func TestStream_CompileExpressionInfo(t *testing.T) { stream.compileExpressionInfo() - // 验证表达式信息已编译 + // Verify that the expression information has been compiled assert.NotNil(t, stream.compiledExprInfo) assert.Len(t, stream.compiledExprInfo, 4) - // 验证每个表达式的编译信息 + // Verify the compilation information for each expression for exprName := range config.FieldExpressions { assert.Contains(t, stream.compiledExprInfo, exprName) info := stream.compiledExprInfo[exprName] @@ -237,7 +237,7 @@ func TestStream_CompileExpressionInfo(t *testing.T) { } } -// TestFieldProcessInfo_EdgeCases 测试字段处理信息边界情况 +// TestFieldProcessInfo_EdgeCases Test fields handle information boundaries func TestFieldProcessInfo_EdgeCases(t *testing.T) { config := types.Config{ SimpleFields: []string{"name", "age"}, @@ -266,7 +266,7 @@ func TestFieldProcessInfo_EdgeCases(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - // 应该不会panic,即使输入不规范 + // There shouldn't be panic, even if the input is not standard assert.NotPanics(t, func() { info := stream.compileSimpleFieldInfo(tt.fieldSpec) assert.NotNil(t, info) @@ -275,9 +275,9 @@ func TestFieldProcessInfo_EdgeCases(t *testing.T) { } } -// TestExpressionProcessInfo_Structure 测试表达式处理信息结构 +// TestExpressionProcessInfo_Structure Test expressions handle information structures func TestExpressionProcessInfo_Structure(t *testing.T) { - // 测试expressionProcessInfo结构的基本功能 + // Test the basic functionality of the expressionProcessInfo structure info := &expressionProcessInfo{ originalExpr: "value + 10", processedExpr: "value + 10", @@ -294,9 +294,9 @@ func TestExpressionProcessInfo_Structure(t *testing.T) { assert.Nil(t, info.compiledExpr) } -// TestFieldProcessInfo_Structure 测试字段处理信息结构 +// TestFieldProcessInfo_Structure Test fields handle information structures func TestFieldProcessInfo_Structure(t *testing.T) { - // 测试fieldProcessInfo结构的基本功能 + // Test the basic functions of the fieldProcessInfo structure info := &fieldProcessInfo{ fieldName: "test_field", outputName: "output_field", @@ -318,9 +318,9 @@ func TestFieldProcessInfo_Structure(t *testing.T) { assert.Equal(t, "field_alias", info.alias) } -// TestStream_CompileFieldProcessInfo_Performance 测试字段处理信息编译性能 +// TestStream_CompileFieldProcessInfo_Performance Test fields handle information compilation performance func TestStream_CompileFieldProcessInfo_Performance(t *testing.T) { - // 创建大量字段的配置 + // Create configurations for a large number of fields fields := make([]string, 100) expressions := make(map[string]types.FieldExpression) @@ -345,12 +345,12 @@ func TestStream_CompileFieldProcessInfo_Performance(t *testing.T) { } }() - // 编译应该快速完成,不会超时 + // Compilation should be completed quickly without timeouts assert.NotPanics(t, func() { stream.compileFieldProcessInfo() }) - // 验证编译结果 + // Verify the compilation results assert.Len(t, stream.compiledFieldInfo, 100) assert.Len(t, stream.compiledExprInfo, 100) } diff --git a/stream/strategy_test.go b/stream/strategy_test.go index d578d64..2c3fdb5 100644 --- a/stream/strategy_test.go +++ b/stream/strategy_test.go @@ -10,7 +10,7 @@ import ( "github.com/stretchr/testify/require" ) -// TestStrategyFactory 测试策略工厂 +// TestStrategyFactory func TestStrategyFactory(t *testing.T) { factory := NewStrategyFactory() @@ -55,7 +55,7 @@ func TestStrategyFactory(t *testing.T) { } } -// TestStrategy_Constructor 测试策略构造函数 +// TestStrategy_Constructor Test policy constructor func TestStrategy_Constructor(t *testing.T) { config := types.Config{ SimpleFields: []string{"name", "age"}, @@ -69,7 +69,7 @@ func TestStrategy_Constructor(t *testing.T) { }() } -// TestBlockingStrategy_ProcessData 测试阻塞策略数据处理 +// TestBlockingStrategy_ProcessData Test blocking policy data processing func TestBlockingStrategy_ProcessData(t *testing.T) { config := types.Config{ SimpleFields: []string{"name", "age"}, @@ -83,7 +83,7 @@ func TestBlockingStrategy_ProcessData(t *testing.T) { }() } -// TestExpansionStrategy_ProcessData 测试扩容策略数据处理 +// TestExpansionStrategy_ProcessData Testing scaling strategy data processing func TestExpansionStrategy_ProcessData(t *testing.T) { config := types.Config{ SimpleFields: []string{"name", "age"}, @@ -97,7 +97,7 @@ func TestExpansionStrategy_ProcessData(t *testing.T) { }() } -// TestDropStrategy_ProcessData 测试丢弃策略数据处理 +// TestDropStrategy_ProcessData Test the processing of discarding policy data func TestDropStrategy_ProcessData(t *testing.T) { config := types.Config{ SimpleFields: []string{"name", "age"}, @@ -111,9 +111,9 @@ func TestDropStrategy_ProcessData(t *testing.T) { }() } -// TestStrategyInitialization 测试策略初始化 +// TestStrategyInitialization func TestStrategyInitialization(t *testing.T) { - // 创建测试配置 + // Create test configurations config := types.Config{ PerformanceConfig: types.PerformanceConfig{ BufferConfig: types.BufferConfig{ @@ -133,14 +133,14 @@ func TestStrategyInitialization(t *testing.T) { }, } - // 创建Stream实例 + // Create a Stream instance stream, err := NewStream(config) if err != nil { t.Fatalf("Failed to create stream: %v", err) } defer stream.Stop() - // 验证策略是否正确设置 + // Verify that the policy is set correctly if stream.dataStrategy == nil { t.Fatal("Data strategy not set") } @@ -150,7 +150,7 @@ func TestStrategyInitialization(t *testing.T) { } } -// TestStrategyProcessData 测试策略数据处理 +// TestStrategyProcessData Processing of test strategy data func TestStrategyProcessData(t *testing.T) { tests := []struct { name string @@ -163,7 +163,7 @@ func TestStrategyProcessData(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - // 创建测试配置 + // Create test configurations config := types.Config{ PerformanceConfig: types.PerformanceConfig{ BufferConfig: types.BufferConfig{ @@ -183,23 +183,23 @@ func TestStrategyProcessData(t *testing.T) { }, } - // 创建Stream实例 + // Create a Stream instance stream, err := NewStream(config) if err != nil { t.Fatalf("Failed to create stream: %v", err) } defer stream.Stop() - // 测试数据处理 + // Test data processing testData := map[string]any{ "test": "data", "id": 1, } - // 这里主要测试策略能够正常调用,不会panic + // The main test here is that the policy can be called normally without panic stream.Emit(testData) - // 验证输入计数增加 + // Validation input count increases if stream.mInput.Value() != 1 { t.Errorf("Expected input count 1, got %d", stream.mInput.Value()) } @@ -207,7 +207,7 @@ func TestStrategyProcessData(t *testing.T) { } } -// TestStrategyCleanup 测试策略清理 +// TestStrategyCleanup func TestStrategyCleanup(t *testing.T) { config := types.Config{ PerformanceConfig: types.PerformanceConfig{ @@ -228,25 +228,25 @@ func TestStrategyCleanup(t *testing.T) { }, } - // 创建Stream实例 + // Create a Stream instance stream, err := NewStream(config) if err != nil { t.Fatalf("Failed to create stream: %v", err) } - // 验证策略存在 + // Verification strategies exist if stream.dataStrategy == nil { t.Fatal("Data strategy not set") } - // 测试停止和清理 + // Testing stops and cleans up stream.Stop() - // 这里主要验证Stop方法能够正常执行,不会panic - // 实际的清理逻辑在各个策略的Cleanup方法中实现 + // Here, the main goal is to verify that the Stop method can execute normally without panic + // The actual cleanup logic is implemented in the Cleanup methods of each policy } -// MockStrategy 模拟策略,用于测试扩展性 +// MockStrategy is a simulation strategy used to test scalability type MockStrategy struct { stream *Stream processed int @@ -254,41 +254,41 @@ type MockStrategy struct { cleaned bool } -// NewMockStrategy 创建模拟策略实例 +// NewMockStrategy creates a simulation strategy example func NewMockStrategy() *MockStrategy { return &MockStrategy{} } -// ProcessData 模拟数据处理 +// ProcessData simulates data processing func (ms *MockStrategy) ProcessData(data map[string]any) { ms.processed++ - // 模拟处理逻辑 + // Simulating processing logic } -// GetStrategyName 获取策略名称 +// GetStrategyName: Get the strategy name func (ms *MockStrategy) GetStrategyName() string { return "mock" } -// Init 初始化模拟策略 +// Init initializes the simulation strategy func (ms *MockStrategy) Init(stream *Stream, config types.PerformanceConfig) error { ms.stream = stream ms.initialized = true return nil } -// Stop 停止并清理模拟策略资源 +// Stop and clear the simulation strategy resources func (ms *MockStrategy) Stop() error { ms.cleaned = true return nil } -// TestCustomStrategy 测试自定义策略的扩展性 +// TestCustomStrategy Tests the scalability of custom strategies func TestCustomStrategy(t *testing.T) { - // 创建自定义策略 + // Create custom policies mockStrategy := NewMockStrategy() - // 创建基本的Stream实例(不通过工厂) + // Create a basic Stream instance (without going through the factory) reg := metrics.NewRegistry() stream := &Stream{ dataChan: make(chan map[string]any, 10), @@ -300,7 +300,7 @@ func TestCustomStrategy(t *testing.T) { mOutputDropped: reg.Counter(OutputDroppedCount), } - // 手动设置策略 + // Manually set the strategy config := types.PerformanceConfig{} err := mockStrategy.Init(stream, config) if err != nil { @@ -309,7 +309,7 @@ func TestCustomStrategy(t *testing.T) { stream.dataStrategy = mockStrategy - // 测试策略功能 + // Test strategy functionality if !mockStrategy.initialized { t.Error("Mock strategy not initialized") } @@ -318,7 +318,7 @@ func TestCustomStrategy(t *testing.T) { t.Errorf("Expected strategy name 'mock', got %s", mockStrategy.GetStrategyName()) } - // 测试数据处理 + // Test data processing testData := map[string]any{"test": "data"} stream.Emit(testData) @@ -326,18 +326,18 @@ func TestCustomStrategy(t *testing.T) { t.Errorf("Expected processed count 1, got %d", mockStrategy.processed) } - // 测试清理 + // Test cleanup stream.Stop() if !mockStrategy.cleaned { t.Error("Mock strategy not cleaned") } } -// TestStrategyRegistration 测试策略注册机制 +// TestStrategyRegistration: Test the strategy registration mechanism func TestStrategyRegistration(t *testing.T) { factory := NewStrategyFactory() - // 测试内置策略是否已注册 + // Test whether the built-in policy is registered registeredStrategies := factory.GetRegisteredStrategies() expectedStrategies := []string{StrategyBlock, StrategyExpand, StrategyDrop} @@ -354,12 +354,12 @@ func TestStrategyRegistration(t *testing.T) { } } - // 测试注册自定义策略 + // Test the registration custom policy factory.RegisterStrategy("kafka", func() DataProcessingStrategy { return &KafkaStrategy{} }) - // 验证自定义策略已注册 + // Verify that the custom policy is registered strategy, err := factory.CreateStrategy("kafka") if err != nil { t.Fatalf("Failed to create kafka strategy: %v", err) @@ -369,44 +369,44 @@ func TestStrategyRegistration(t *testing.T) { t.Errorf("Expected strategy name 'kafka', got %s", strategy.GetStrategyName()) } - // 测试注销策略 + // Test the deregistration strategy factory.UnregisterStrategy("kafka") strategy, err = factory.CreateStrategy("kafka") if err != nil { t.Fatalf("Failed to create default strategy: %v", err) } - // 注销后应该返回默认的丢弃策略 + // After logout, the default discarding policy should be restored if strategy.GetStrategyName() != StrategyDrop { t.Errorf("Expected default strategy '%s', got %s", StrategyDrop, strategy.GetStrategyName()) } } -// KafkaStrategy Kafka削峰策略示例 +// KafkaStrategy Kafka Peak Shaving Strategy Example type KafkaStrategy struct { stream *Stream } -// ProcessData 实现Kafka削峰数据处理 +// ProcessData implements Kafka peak shaving data processing func (ks *KafkaStrategy) ProcessData(data map[string]any) { - // 模拟Kafka削峰逻辑 - // 实际实现中会将数据发送到Kafka队列 + // Simulates Kafka peak-cutting logic + // In actual implementations, data is sent to the Kafka queue logger.Debug("Data sent to Kafka for peak shaving") } -// GetStrategyName 获取策略名称 +// GetStrategyName: Get the strategy name func (ks *KafkaStrategy) GetStrategyName() string { return "kafka" } -// Init 初始化Kafka策略 +// Init initializes the Kafka strategy func (ks *KafkaStrategy) Init(stream *Stream, config types.PerformanceConfig) error { ks.stream = stream - // 这里可以初始化Kafka连接等 + // Here, you can initialize Kafka connections, etc return nil } -// Stop 停止并清理Kafka策略资源 +// Stop and clean up Kafka's strategic resources func (ks *KafkaStrategy) Stop() error { - // 这里可以关闭Kafka连接等 + // You can disable Kafka connections and similar connections here return nil } diff --git a/stream/stream.go b/stream/stream.go index f1a4d94..44c3162 100644 --- a/stream/stream.go +++ b/stream/stream.go @@ -116,15 +116,15 @@ type Stream struct { groupOutputNames []string // Unnest function optimization flags - // hasUnnestFunction 标识查询是否使用了 unnest 函数,在预处理阶段确定 - // 用于优化 expandUnnestResults 函数的性能,避免不必要的字段遍历检查 + // Whether the hasUnnestFunction identifier query uses the unnest function is determined during the preprocessing phase + // Used to optimize the performance of the expandUnnestResults function and avoid unnecessary field traversal checks hasUnnestFunction bool // Whether the query uses unnest function, determined during preprocessing - // 分析函数状态机引擎,lazy 初始化。直连路径在 WHERE 前求值。 + // Analyze function state machine engine, lazy initialization. Direct path evaluation before WHERE is evaluated. analytic *AnalyticEngine analyticOnce sync.Once - // CEP(MATCH_RECOGNIZE)引擎适配器。构造期(StreamFactory)初始化,消除懒初始化并发读。 + // CEP (MATCH_RECOGNIZE) engine adapter. StreamFactory initialization eliminates lazy initialization and concurrent reads. cep *cepRunner } @@ -239,7 +239,7 @@ func (s *Stream) Start() { } s.startMu.Unlock() if s.cep != nil { - s.cep.Start() // 启动 WITHIN 主动过期 sweeper + s.cep.Start() // Activate the WITHIN active expired sweeper } go func() { defer s.lifecycle.Done() @@ -293,14 +293,14 @@ func (s *Stream) Stop() { // (e.g. a rulego component Destroy). s.waitLifecycle() - // 停止 CEP sweeper:数据处理 goroutine 已 join,不再有并发 Process;紧接的 Flush 看到静止引擎。 + // Stop CEP sweeper: The data processing goroutine has joined, and there are no longer concurrent processes; Immediately after, Flush saw the engine at a standstill. if s.cep != nil { s.cep.Stop() } - // 冲刷 CEP 流末未界匹配(如未闭合的 A+ 突发)。此时所有 goroutine 已 join、worker pool - // 已退出,故同步派发到 sink(不经 pool)——若在 close(done) 后仍走 pool,worker 已退出会 - // 使 Flush 结果丢失。 + // Flushing CEP end-of-flow unbounded matches (such as unclosed A+ bursts). At this point, all goroutines have joined and worked in the pool + // has already exited, so the worker is distributed to the sink (without going through the pool)—if the worker still goes through the pool after closing(done), the worker will have exited + // Causes the Flush result to be lost. if s.cep != nil { s.emitCepFlushSync(s.projectCep(s.cep.engine.Flush())) } @@ -395,8 +395,8 @@ func (s *Stream) IsCEPQuery() bool { return s.config.Mode == types.ExecCEP } -// projectCep 对 MATCH_RECOGNIZE 原始输出行做外层 SELECT 投影(复用直连路径投影), -// 供 processCEP 与 Stop-Flush 共用,保证两条产出路径输出形态一致。 +// projectCep performs an outer SELECT projection (multiplexing direct path projection) on the original output line of MATCH_RECOGNIZE, +// Shared by processCEP and Stop-Flush to ensure consistent output patterns for both output paths. func (s *Stream) projectCep(raw []map[string]any) []map[string]any { out := make([]map[string]any, 0, len(raw)) for _, mrRow := range raw { @@ -407,8 +407,8 @@ func (s *Stream) projectCep(raw []map[string]any) []map[string]any { return out } -// emitCepResults 派发 CEP 匹配输出行到 result channel 与 sinks。 -// mOutput 由 sendResultNonBlocking 计一次(与窗口/直连异步路径一致)。 +// emitCepResults sends CEP matching output lines to the result channel and sinks. +// mOutput is counted once by sendResultNonBlocking (consistent with window/direct asynchronous path). func (s *Stream) emitCepResults(results []map[string]any) { if len(results) == 0 { return @@ -417,18 +417,18 @@ func (s *Stream) emitCepResults(results []map[string]any) { s.callSinksAsync(results) } -// emitCepFlushSync 在 Stop 末尾同步派发 CEP Flush 输出。此时 worker pool 已随 done 退出, -// 故在 Stop goroutine 内直接调用 sink(同步、不经 pool),避免未闭合匹配的 Flush 结果丢失。 +// emitCepFlushSync synchronously sends the CEP Flush output at the end of Stop. At this point, the worker pool has already exited with 'done', +// Therefore, the Stop goroutine directly calls sinks (synchronous, without pooling) to avoid losing flushing results in unclosed matches. func (s *Stream) emitCepFlushSync(results []map[string]any) { if len(results) == 0 { return } - s.sendResultForFlush(results) // 尽量送达 resultChan(短阻塞),不静默丢 + s.sendResultForFlush(results) // Try to deliver resultChan (short block) without silently discarding it s.invokeSinksInline(results) } -// invokeSinksInline 在当前 goroutine 同步调用全部 sinks 与 syncSinks(带 recover), -// 供 Stop-Flush 等 worker pool 已退出场景复用。 +// invokeSinksInline synchronously calls all sinks and syncSinks (with recover) in the current goroutine, +// Allows Stop-Flush and other worker pools to be reused in deleted scenarios. func (s *Stream) invokeSinksInline(results []map[string]any) { s.sinksMux.RLock() sinks := make([]func([]map[string]any), len(s.sinks)) @@ -452,8 +452,8 @@ func (s *Stream) invokeSinksInline(results []map[string]any) { } } -// sendResultForFlush 向 resultChan 尽量投递:先非阻塞试,满则短时阻塞等活跃消费者; -// 超时计 drop(不静默)。供 Flush 路径使用——未闭合匹配是流末关键产物,优先保投递。 +// sendResultForFlush tries to deliver to resultChan: first non-blocking test, then short-term blocking when full, etc., active consumers; +// Timer drop (not silent). For use by Flush paths—unclosed matching is a key product at the end of the stream, prioritized for guaranteed delivery. func (s *Stream) sendResultForFlush(results []map[string]any) { select { case s.resultChan <- results: @@ -470,7 +470,7 @@ func (s *Stream) sendResultForFlush(results []map[string]any) { } } -// ensureAnalytic 懒初始化分析函数状态机引擎(SELECT 分析函数 + WHERE 占位符调用统一管理)。 +// ensureAnalytic lazily initializes the analysis function state machine engine (SELECT analysis function + WHERE placeholder call unified management). func (s *Stream) ensureAnalytic() { s.analyticOnce.Do(func() { if len(s.config.AnalyticFields) == 0 && len(s.config.WhereAnalyticCalls) == 0 { @@ -496,15 +496,15 @@ func (s *Stream) ensureAnalytic() { }) } -// evalAnalytic 求值分析函数并把结果注入 dataMap(供 WHERE 占位符引用),返回结果供投影。 -// 在 WHERE 之前调用(分析函数最先求值,不受 WHERE 影响)。 +// evalAnalytic evaluates and analyzes the function, injects the results into dataMap (for WHERE placeholder references), and returns the results for projection. +// Call before WHERE (the analysis function evaluates first, not affected by WHERE). func (s *Stream) evalAnalytic(dataMap map[string]any) map[string]any { s.ensureAnalytic() if s.analytic == nil || !s.analytic.HasFields() { return nil } results := s.analytic.Evaluate(dataMap) - // SELECT 分析函数注入 dataMap:多列函数按 prefix+列名 扇出,供 WHERE/HAVING 引用。 + // SELECT Injection of analysis function dataMap: Multi-column functions are fanned out by prefix+ column name for WHERE/HAVING reference. for _, af := range s.config.AnalyticFields { v, ok := results[af.Alias] if !ok { @@ -520,7 +520,7 @@ func (s *Stream) evalAnalytic(dataMap map[string]any) map[string]any { } dataMap[af.Alias] = v } - // WHERE 占位符调用:注入占位符键值,供改写后的 WHERE 引用。 + // WHERE Placeholder Call: Injects placeholder key values for referenced by the rewritten WHERE page. for _, wc := range s.config.WhereAnalyticCalls { if v, ok := results[wc.Placeholder]; ok { dataMap[wc.Placeholder] = v @@ -529,7 +529,7 @@ func (s *Stream) evalAnalytic(dataMap map[string]any) map[string]any { return results } -// projectAnalytic 把 SELECT 分析函数结果写入投影输出:单列按 alias,多列按 prefix+列名 扇出。 +// projectAnalytic writes the SELECT analysis function results into projected output: single columns press alias, multiple columns press prefix+ column name fan-out. func (s *Stream) projectAnalytic(result map[string]any, analyticResults map[string]any) { if analyticResults == nil { return @@ -547,7 +547,7 @@ func (s *Stream) projectAnalytic(result map[string]any, analyticResults map[stri } continue } - // changed_col 未变化时返回 nil:投影时省略该字段(避免 null 刷屏)。 + // changed_col Returns nil when no changes occur: omitted this field during projection (to avoid null flooding). if af.FuncName == "changed_col" && v == nil { continue } @@ -555,8 +555,8 @@ func (s *Stream) projectAnalytic(result map[string]any, analyticResults map[stri } } -// hasOmitEmptyAnalytic 是否含 changed_col/changed_cols 这类变化检测函数: -// 当它们的输出全为空(无变化)时,整行抑制输出。 +// hasOmitEmptyAnalytic is included in changed_col/changed_cols Function for detecting changes like this: +// When all their outputs are empty (unchanged), the entire line suppresses output. func (s *Stream) hasOmitEmptyAnalytic() bool { for _, af := range s.config.AnalyticFields { if af.MultiColumn || af.FuncName == "changed_col" { @@ -566,11 +566,11 @@ func (s *Stream) hasOmitEmptyAnalytic() bool { return false } -// hasAnalyticFields 是否有 SELECT 分析函数字段。 +// hasAnalyticFields Whether there is a SELECT analysis function numbered segment. func (s *Stream) hasAnalyticFields() bool { return len(s.config.AnalyticFields) > 0 } -// applyWindowAnalytic 在窗口查询里对结果行求值分析函数(状态跨窗口保留), -// 把输出合并进结果行,剥离内联聚合隐藏键。返回 false 表示该行应抑制(变化检测无变化)。 +// applyWindowAnalytic evaluates and analyzes the result row in window queries (status is kept across windows), +// Merge outputs into the result row, strip off the inline aggregated hidden keys. Returning false means the line should be suppressed (change detection shows no change). func (s *Stream) applyWindowAnalytic(row map[string]any) bool { s.ensureAnalytic() if s.analytic == nil || !s.analytic.HasFields() { @@ -594,20 +594,20 @@ func (s *Stream) applyWindowAnalytic(row map[string]any) bool { } continue } - // changed_col 未变化时返回 nil:投影省略该字段。 + // changed_col Returns nil when unchanged: Omit this field for projection. if af.FuncName == "changed_col" && v == nil { continue } row[af.Alias] = v changedAny = true } - // 剥离内联聚合隐藏键(不进最终输出)。 + // Strip the inline aggregated hidden key (do not input the final output). for k := range row { if strings.HasPrefix(k, "__winagg_") { delete(row, k) } } - // omitEmpty:仅选了变化检测函数且本次无变化 → 抑制整行。 + // omitEmpty: Only the change detection function is selected and there is no change this time→ Suppresses the entire line. if !changedAny && s.hasOmitEmptyAnalytic() { return false } @@ -623,8 +623,8 @@ func (s *Stream) applyWindowAnalytic(row map[string]any) bool { // - map[string]any: processed result data, returns nil if doesn't match filter condition // - error: processing error, returns error for aggregation queries func (s *Stream) ProcessSync(data map[string]any) (map[string]any, error) { - // 同步单事件返回仅适用于直连路径:窗口聚合与 CEP 模式匹配都跨多事件,无法单事件返回。 - // 窗口判定沿用 NeedWindow(兼容直接构造 config 的用例),CEP 判定用 Mode。 + // Synchronous single-event return is only applicable to direct connection paths: window aggregation and CEP mode matching both span multiple events and cannot return single events. + // Window determination uses NeedWindow (compatible with direct config construction use cases), CEP uses Mode. if s.config.NeedWindow { return nil, fmt.Errorf("Synchronous processing is not supported for aggregation queries.") } @@ -637,8 +637,8 @@ func (s *Stream) ProcessSync(data map[string]any) (map[string]any, error) { return s.processDirectDataSync(data) } -// enrichData 解析流-表 JOIN 富化。返回富化后的 dataMap、是否保留、JOIN 错误。 -// 无 JOIN 时零开销直返。同步直连/异步直连/窗口前置三路径共用。 +// enrichData Stream-Table JOIN Enrichment. Returns the enriched dataMap, whether to retain, JOIN error. +// No JOIN with zero overhead direct return. Synchronous direct connection, asynchronous direct connection, and window front-end are shared by three paths. func (s *Stream) enrichData(data map[string]any) (dataMap map[string]any, keep bool, err error) { dataMap = data if !s.hasJoin() { @@ -649,13 +649,13 @@ func (s *Stream) enrichData(data map[string]any) (dataMap map[string]any, keep b return dataMap, false, jerr } if !k { - return dataMap, false, nil // INNER JOIN 无匹配:丢弃 + return dataMap, false, nil // INNER JOIN: No match: Discarded } return wm, true, nil } -// applyWhereAndAnalytic 按 WHERE 是否引用分析函数决定求值序,并应用 WHERE 过滤。 -// 返回分析结果(供投影)与是否通过过滤。同步/异步直连路径共用。 +// applyWhereAndAnalytic determines the evaluation order based on whether the analysis function references WHERE and applies WHERE filtering. +// Returns analysis results (for projection) and whether they pass filtering. Synchronous/asynchronous direct connection paths are shared. func (s *Stream) applyWhereAndAnalytic(dataMap map[string]any) (analyticResults map[string]any, keep bool) { whereUsesAnalytic := len(s.config.WhereAnalyticCalls) > 0 if whereUsesAnalytic { @@ -670,8 +670,8 @@ func (s *Stream) applyWhereAndAnalytic(dataMap map[string]any) (analyticResults return analyticResults, true } -// projectDirectRow 投影 SELECT 字段(表达式/简单字段/分析函数),含 omitEmpty 抑制。 -// emit=false 表示该行被 omitEmpty 抑制、不应输出。同步/异步直连路径共用。 +// projectDirectRow projects SELECT fields (expressions/simple fields/parser functions), including omitEmpty suppression. +// emit=false means the line is suppressed by omitEmpty and should not be output. Synchronous/asynchronous direct connection paths are shared. func (s *Stream) projectDirectRow(dataMap, analyticResults map[string]any) (result map[string]any, emit bool) { estimatedSize := len(s.config.FieldExpressions) + len(s.config.SimpleFields) if estimatedSize < 8 { diff --git a/stream/stream_factory.go b/stream/stream_factory.go index d25e484..6939bc5 100644 --- a/stream/stream_factory.go +++ b/stream/stream_factory.go @@ -98,8 +98,8 @@ func (sf *StreamFactory) createStreamWithUnifiedConfig(config types.Config) (*St return nil, err } - // CEP 模式:构造期编译并实例化引擎。fail-fast(编译错误在 Execute 即暴露), - // 且引擎在 Start 派生 goroutine 前就绪,消除原懒初始化对 s.cep 的并发读。 + // CEP Pattern: Builds and instantiates the engine. fail-fast (compilation errors are exposed during execute), + // The engine is ready before Start derivings goroutines, eliminating the original lazy initialization concurrent read to s.cep. if config.Mode == types.ExecCEP && config.MatchRecognize != nil { if err := cep.Validate(config.MatchRecognize); err != nil { return nil, fmt.Errorf("MATCH_RECOGNIZE invalid: %w", err) diff --git a/stream/stream_field_test.go b/stream/stream_field_test.go index eb00b50..11cee13 100644 --- a/stream/stream_field_test.go +++ b/stream/stream_field_test.go @@ -10,7 +10,7 @@ import ( "github.com/stretchr/testify/require" ) -// TestSelectStarWithExpressionFields 测试SELECT *与表达式字段的组合 +// TestSelectStarWithExpressionFields tests the combination of SELECT * and expression fields func TestSelectStarWithExpressionFields(t *testing.T) { tests := []struct { name string @@ -64,7 +64,7 @@ func TestSelectStarWithExpressionFields(t *testing.T) { }, expectedFields: map[string]any{ "name": "ALICE", - "age": 60.0, // 表达式结果 + "age": 60.0, // Expression result "status": "active", }, }, @@ -97,7 +97,7 @@ func TestSelectStarWithExpressionFields(t *testing.T) { require.NoError(t, err) defer stream.Stop() - // 收集结果 + // Collect the results var mu sync.Mutex var results []any stream.AddSink(func(result []map[string]any) { @@ -109,10 +109,10 @@ func TestSelectStarWithExpressionFields(t *testing.T) { stream.Start() stream.Emit(tt.testData) - // 等待处理完成 + // Wait for processing to complete time.Sleep(100 * time.Millisecond) - // 验证结果 + // Verify the results mu.Lock() defer mu.Unlock() @@ -123,7 +123,7 @@ func TestSelectStarWithExpressionFields(t *testing.T) { actual, exists := resultData[field] assert.True(t, exists, "Field %s should exist", field) if expected != nil { - // 处理数值类型的比较 + // Handle numerical types comparison if expectedFloat, ok := expected.(float64); ok { if actualFloat, ok := actual.(float64); ok { assert.InEpsilon(t, expectedFloat, actualFloat, 0.0001) @@ -141,7 +141,7 @@ func TestSelectStarWithExpressionFields(t *testing.T) { } } -// TestFieldProcessor 测试字段处理器 +// TestFieldProcessor Tests field processors func TestFieldProcessor(t *testing.T) { tests := []struct { name string @@ -222,14 +222,14 @@ func TestFieldProcessor(t *testing.T) { require.Len(t, results, 1) resultData := results[0].([]map[string]any)[0] - // 验证期望的字段都存在 + // The fields to verify the desired existence are present 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 it is not "*", there are no extra fields for validation if len(tt.simpleFields) == 1 && tt.simpleFields[0] != "*" { assert.Len(t, resultData, len(tt.expected), "Should only have expected fields") } @@ -237,7 +237,7 @@ func TestFieldProcessor(t *testing.T) { } } -// TestExpressionEvaluation 测试表达式计算 +// TestExpressionEvaluation Calculates test expressions func TestExpressionEvaluation(t *testing.T) { tests := []struct { name string @@ -317,7 +317,7 @@ func TestExpressionEvaluation(t *testing.T) { actual, exists := resultData["result"] assert.True(t, exists, "Result field should exist") - // 处理数值类型的比较 + // Handle numerical types comparison if expectedFloat, ok := tt.expected.(float64); ok { if actualFloat, ok := actual.(float64); ok { assert.InEpsilon(t, expectedFloat, actualFloat, 0.0001) diff --git a/stream/stream_test.go b/stream/stream_test.go index 79d7e36..256961d 100644 --- a/stream/stream_test.go +++ b/stream/stream_test.go @@ -13,7 +13,7 @@ import ( "github.com/stretchr/testify/require" ) -// TestStreamBasicOperations 测试Stream基本操作(合并了构造函数、添加数据、获取结果等测试) +// TestStreamBasicOperations (integrates constructors, add data, get results, and other tests) func TestStreamBasicOperations(t *testing.T) { tests := []struct { name string @@ -83,16 +83,16 @@ func TestStreamBasicOperations(t *testing.T) { } }() - // 根据测试类型执行不同的测试逻辑 + // Different test logic is executed depending on the type of test switch tt.testFunc { case "constructor": - // 验证stream创建成功 + // Verify that the stream was successfully created assert.NotNil(t, stream) assert.NotNil(t, stream.dataChan) assert.NotNil(t, stream.resultChan) case "addData": - // 测试添加数据 + // Test adding data testData := map[string]any{ "name": "test", "age": 25, @@ -101,17 +101,17 @@ func TestStreamBasicOperations(t *testing.T) { assert.True(t, sent) case "getResults": - // 测试获取结果通道 + // Test the results channel resultsChan := stream.GetResultsChan() assert.NotNil(t, resultsChan) case "withWindow": - // 验证窗口配置 + // Verification window configuration assert.NotNil(t, stream.Window) assert.True(t, stream.config.NeedWindow) case "threadSafety": - // 测试并发安全性 + // Testing concurrency security var wg sync.WaitGroup for i := 0; i < 10; i++ { wg.Add(1) @@ -130,7 +130,7 @@ func TestStreamBasicOperations(t *testing.T) { } } -// TestStreamBasicFunctionality 测试Stream基本功能 +// TestStreamBasicFunctionality Tests the basic functionality of Stream func TestStreamBasicFunctionality(t *testing.T) { tests := []struct { name string @@ -204,27 +204,27 @@ func TestStreamBasicFunctionality(t *testing.T) { require.NoError(t, err) } - // 添加 Sink 函数来捕获结果 + // Add the Sink function to capture the results resultChan := make(chan any, 1) strm.AddSink(func(result []map[string]any) { select { case resultChan <- result: default: - // 防止阻塞 + // Prevents blockages } }) strm.Start() - // 发送测试数据 + // Send test data for _, data := range tt.testData { strm.Emit(data) } - // 等待窗口关闭并触发结果 + // Wait for the window to close and trigger the result time.Sleep(700 * time.Millisecond) - // 等待结果 + // Wait for the results ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) defer cancel() @@ -236,7 +236,7 @@ func TestStreamBasicFunctionality(t *testing.T) { t.Fatal("No results received within 3 seconds") } - // 验证结果 + // Verify the results require.NotNil(t, actual) assert.IsType(t, []map[string]any{}, actual) resultMap := actual.([]map[string]any) @@ -250,7 +250,7 @@ func TestStreamBasicFunctionality(t *testing.T) { } } -// TestStreamWithoutFilter 测试无过滤器的流处理 +// TestStreamWithoutFilter: Tests stream processing without filters func TestStreamWithoutFilter(t *testing.T) { config := types.Config{ WindowConfig: types.WindowConfig{ @@ -281,13 +281,13 @@ func TestStreamWithoutFilter(t *testing.T) { strm.Emit(data) } - // 捕获结果 + // Capture the results resultChan := make(chan any) strm.AddSink(func(result []map[string]any) { resultChan <- result }) - // 等待窗口触发 + // Wait for the window to trigger time.Sleep(3 * time.Second) ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) @@ -324,7 +324,7 @@ func TestStreamWithoutFilter(t *testing.T) { } } -// TestStreamRefactoring 测试重构后的Stream功能 +// TestStreamRefactoring tests the restructured Stream functionality func TestStreamRefactoring(t *testing.T) { config := types.Config{ SimpleFields: []string{"name", "age"}, @@ -345,21 +345,21 @@ func TestStreamRefactoring(t *testing.T) { "category": "A", } - // 启动Stream + // Start Stream stream.Start() - // 发送测试数据 + // Send test data stream.Emit(testData) - // 等待处理完成 + // Wait for processing to complete time.Sleep(100 * time.Millisecond) - // 获取统计信息 + // Get statistics stats := stream.GetStats() assert.Equal(t, int64(1), stats[InputCount]) } -// TestStreamFactory 测试StreamFactory功能 +// TestStreamFactory tests the StreamFactory functionality func TestStreamFactory(t *testing.T) { factory := NewStreamFactory() require.NotNil(t, factory) @@ -369,7 +369,7 @@ func TestStreamFactory(t *testing.T) { NeedWindow: false, } - // 测试不同的创建方法 + // Test different creation methods tests := []struct { name string create func() (*Stream, error) @@ -389,9 +389,9 @@ func TestStreamFactory(t *testing.T) { } } -// TestMigratedFunctions 测试迁移后的函数 +// TestMigratedFunctions tests the migrated functions func TestMigratedFunctions(t *testing.T) { - // 测试性能评估函数 + // Test performance evaluation function tests := []struct { name string dataUsage float64 @@ -413,13 +413,13 @@ func TestMigratedFunctions(t *testing.T) { } } -// TestStreamConstructors 测试各种Stream构造函数 +// TestStreamConstructors tests various Stream constructors func TestStreamConstructors(t *testing.T) { config := types.Config{ SimpleFields: []string{"name", "age"}, } - // 测试高性能Stream + // Testing high-performance streams highPerfStream, err := NewStreamWithHighPerformance(config) require.NoError(t, err) require.NotNil(t, highPerfStream) @@ -429,7 +429,7 @@ func TestStreamConstructors(t *testing.T) { } }() - // 测试低延迟Stream + // Testing low-latency streams lowLatencyStream, err := NewStreamWithLowLatency(config) require.NoError(t, err) require.NotNil(t, lowLatencyStream) @@ -439,7 +439,7 @@ func TestStreamConstructors(t *testing.T) { } }() - // 测试自定义性能配置Stream + // Test custom performance configuration Stream perfConfig := types.PerformanceConfig{ WorkerConfig: types.WorkerConfig{ SinkWorkerCount: 10, @@ -458,7 +458,7 @@ func TestStreamConstructors(t *testing.T) { }() } -// TestStreamFilterRegistration 测试过滤器注册功能 +// TestStreamFilterRegistration Test filter registration function func TestStreamFilterRegistration(t *testing.T) { config := types.Config{ SimpleFields: []string{"name", "age"}, @@ -471,36 +471,36 @@ func TestStreamFilterRegistration(t *testing.T) { } }() - // 测试空过滤器 + // Test the empty filter err = stream.RegisterFilter("") require.NoError(t, err) - // 测试简单条件过滤器 + // Test the simple condition filter err = stream.RegisterFilter("age > 18") require.NoError(t, err) - // 测试带反引号的过滤器 + // Test filters with backquotes err = stream.RegisterFilter("`user_name` == 'test'") require.NoError(t, err) - // 测试LIKE语法 + // Test LIKE syntax err = stream.RegisterFilter("name LIKE '%test%'") require.NoError(t, err) - // 测试IS NULL语法 + // Test IS NULL syntax err = stream.RegisterFilter("age IS NULL") require.NoError(t, err) - // 测试IS NOT NULL语法 + // Test the IS NOT NULL syntax err = stream.RegisterFilter("name IS NOT NULL") require.NoError(t, err) - // 测试复杂条件 + // Testing complex conditions err = stream.RegisterFilter("age > 18 && name LIKE '%test%' || `user_id` IS NOT NULL") require.NoError(t, err) } -// TestStreamAggregationQuery 测试聚合查询功能 +// TestStreamAggregationQuery tests aggregation query functionality func TestStreamAggregationQuery(t *testing.T) { config := types.Config{ SimpleFields: []string{"name", "age"}, @@ -522,11 +522,11 @@ func TestStreamAggregationQuery(t *testing.T) { } }() - // 测试是否为聚合查询 + // Test whether the query is aggregated isAggregation := stream.IsAggregationQuery() require.True(t, isAggregation) - // 测试非聚合查询 + // Test non-aggregated queries simpleConfig := types.Config{ SimpleFields: []string{"name", "age"}, } @@ -542,7 +542,7 @@ func TestStreamAggregationQuery(t *testing.T) { require.False(t, isSimpleAggregation) } -// TestStreamSyncProcessing 测试同步处理功能 +// TestStreamSyncProcessing Testing synchronization processing function func TestStreamSyncProcessing(t *testing.T) { config := types.Config{ SimpleFields: []string{"name", "age"}, @@ -555,7 +555,7 @@ func TestStreamSyncProcessing(t *testing.T) { } }() - // 测试同步处理数据 + // Synchronized data processing for testing testData := map[string]any{ "name": "test", "age": 25, @@ -568,14 +568,14 @@ func TestStreamSyncProcessing(t *testing.T) { require.Equal(t, 25, result["age"]) } -// TestStreamErrorHandling 测试错误处理 +// TestStreamErrorHandling Test error handling func TestStreamErrorHandling(t *testing.T) { - // 测试无效配置 + // Invalid testing configuration invalidConfig := types.Config{ - // 故意留空以测试错误处理 + // Deliberately leave blanks to test for error handling } stream, err := NewStream(invalidConfig) - // 这个可能会成功,因为配置验证可能比较宽松 + // This might succeed because configuration verification can be relatively lenient // require.Error(t, err) if stream != nil { @@ -583,16 +583,16 @@ func TestStreamErrorHandling(t *testing.T) { close(stream.done) }() - // 测试无效过滤器 + // Test for ineffective filters err = stream.RegisterFilter("invalid syntax !!!") - // 这个应该会失败 + // This one is likely to fail if err != nil { require.Contains(t, err.Error(), "compile filter error") } } } -// TestStreamConcurrency 测试并发安全性 +// TestStreamConcurrency tests concurrency security func TestStreamConcurrency(t *testing.T) { config := types.Config{ SimpleFields: []string{"name", "age"}, @@ -605,7 +605,7 @@ func TestStreamConcurrency(t *testing.T) { } }() - // 启动多个goroutine并发发送数据 + // Starts multiple goroutines to send data concurrently const numGoroutines = 10 const numMessages = 100 @@ -628,7 +628,7 @@ func TestStreamConcurrency(t *testing.T) { wg.Wait() } -// TestStreamPerformance 测试性能相关功能 +// TestStreamPerformance tests performance-related features func TestStreamPerformance(t *testing.T) { config := types.Config{ SimpleFields: []string{"name", "age"}, @@ -637,7 +637,7 @@ func TestStreamPerformance(t *testing.T) { SinkWorkerCount: 5, }, BufferConfig: types.BufferConfig{ - DataChannelSize: 1000, // 增加缓冲区大小以容纳所有消息 + DataChannelSize: 1000, // Increase the buffer size to accommodate all messages }, }, } @@ -645,10 +645,10 @@ func TestStreamPerformance(t *testing.T) { require.NoError(t, err) defer stream.Stop() - // 启动流处理 + // Start stream processing stream.Start() - // 测试大量数据处理 + // Testing large amounts of data processing const numMessages = 1000 for i := 0; i < numMessages; i++ { data := map[string]any{ @@ -658,11 +658,11 @@ func TestStreamPerformance(t *testing.T) { stream.Emit(data) } - // 等待处理完成 + // Wait for processing to complete time.Sleep(100 * time.Millisecond) } -// TestStreamLifecycle 测试Stream生命周期 +// TestStreamLifecycle TestStream Lifecycle func TestStreamLifecycle(t *testing.T) { config := types.Config{ SimpleFields: []string{"name", "age"}, @@ -670,10 +670,10 @@ func TestStreamLifecycle(t *testing.T) { stream, err := NewStream(config) require.NoError(t, err) - // 测试启动 + // Test launched stream.Start() - // 发送一些数据 + // Send some data for i := 0; i < 10; i++ { data := map[string]any{ "name": fmt.Sprintf("user_%d", i), @@ -682,14 +682,14 @@ func TestStreamLifecycle(t *testing.T) { stream.Emit(data) } - // 测试停止 + // The test stopped stream.Stop() - // 验证停止后不能发送数据(这取决于具体实现) - // 这里只是测试Stop方法不会panic + // After authentication stops, data cannot be sent (depending on the specific implementation) + // This is just testing that the Stop method does not panic } -// TestConvertToAggregationFields 测试聚合字段转换 +// TestConvertToAggregationFields tests aggregation field conversions func TestConvertToAggregationFields(t *testing.T) { selectFields := map[string]aggregator.AggregateType{ "avg_age": aggregator.Avg, @@ -706,7 +706,7 @@ func TestConvertToAggregationFields(t *testing.T) { fields := convertToAggregationFields(selectFields, fieldAlias) require.Len(t, fields, 3) - // 验证字段转换结果 + // Verify the field conversion results for _, field := range fields { switch field.OutputAlias { case "avg_age": @@ -722,7 +722,7 @@ func TestConvertToAggregationFields(t *testing.T) { } } -// TestStreamWithWindowAndAggregation 测试带窗口和聚合的Stream +// TestStreamWithWindowAndAggregation: Tests streams with windows and aggregation func TestStreamWithWindowAndAggregation(t *testing.T) { config := types.Config{ SimpleFields: []string{"name", "age"}, @@ -748,14 +748,14 @@ func TestStreamWithWindowAndAggregation(t *testing.T) { } }() - // 注册过滤器 + // Register the filter err = stream.RegisterFilter("age > 0") require.NoError(t, err) - // 启动Stream + // Start Stream stream.Start() - // 发送数据 + // Send data for i := 0; i < 50; i++ { data := map[string]any{ "name": fmt.Sprintf("user_%d", i), @@ -764,18 +764,18 @@ func TestStreamWithWindowAndAggregation(t *testing.T) { stream.Emit(data) } - // 等待处理完成 + // Wait for processing to complete time.Sleep(200 * time.Millisecond) stream.Stop() } -// TestDataChannelExpansion 测试数据通道动态扩容功能 +// TestDataChannelExpansion: Dynamic capacity expansion of the test data channel func TestDataChannelExpansion(t *testing.T) { config := types.Config{ SimpleFields: []string{"name", "value"}, PerformanceConfig: types.PerformanceConfig{ BufferConfig: types.BufferConfig{ - DataChannelSize: 10, // 小容量便于测试扩容 + DataChannelSize: 10, // Small capacity makes testing and expansion easier ResultChannelSize: 100, }, WorkerConfig: types.WorkerConfig{ @@ -789,10 +789,10 @@ func TestDataChannelExpansion(t *testing.T) { require.NoError(t, err) defer stream.Stop() - // 启动流 + // Startup flow stream.Start() - // 发送数据测试通道功能 + // Send data to test channel function for i := 0; i < 8; i++ { data := map[string]any{ "name": "test", @@ -801,10 +801,10 @@ func TestDataChannelExpansion(t *testing.T) { stream.Emit(data) } - // 等待处理 + // Waiting for processing time.Sleep(100 * time.Millisecond) - // 验证数据处理 + // Validation data processing stats := stream.GetStats() assert.NotNil(t, stats) assert.True(t, stats[InputCount] > 0) @@ -823,14 +823,14 @@ func TestResultHandlerGetResultsChan(t *testing.T) { } }() - // 测试获取结果通道 + // Test the results channel resultsChan := stream.GetResultsChan() assert.NotNil(t, resultsChan) } -// TestConcurrentDataChannelExpansion 测试并发数据通道扩容 +// TestConcurrentDataChannelExpansion: Tests concurrent data channel expansion func TestConcurrentDataChannelExpansion(t *testing.T) { - // 创建流 + // Create streams config := types.Config{ SimpleFields: []string{"id", "message"}, PerformanceConfig: types.PerformanceConfig{ @@ -848,10 +848,10 @@ func TestConcurrentDataChannelExpansion(t *testing.T) { require.NoError(t, err) defer stream.Stop() - // 启动流 + // Startup flow stream.Start() - // 并发添加大量数据 + // Concurrent addition of large amounts of data var wg sync.WaitGroup for i := 0; i < 100; i++ { wg.Add(1) @@ -868,14 +868,14 @@ func TestConcurrentDataChannelExpansion(t *testing.T) { wg.Wait() time.Sleep(100 * time.Millisecond) - // 验证流仍在正常运行 + // The validation flow is still running normally assert.NotNil(t, stream) } -// TestExpandDataChannelDirectly 直接测试expandDataChannel函数 -// 提高expandDataChannel函数的覆盖率 +// TestExpandDataChannelDirectly tests the expandDataChannel function +// Increase the coverage of the expandDataChannel function func TestExpandDataChannelDirectly(t *testing.T) { - // 创建一个Stream实例用于测试 + // Create a Stream instance for testing config := types.Config{ SimpleFields: []string{"test"}, PerformanceConfig: types.PerformanceConfig{ @@ -890,7 +890,7 @@ func TestExpandDataChannelDirectly(t *testing.T) { require.NoError(t, err) defer stream.Stop() - // 填充通道到80%以上以触发扩容条件 + // Fill the channel above 80% to trigger scaling conditions for i := 0; i < 9; i++ { select { case stream.dataChan <- map[string]any{"test": i}: @@ -899,20 +899,20 @@ func TestExpandDataChannelDirectly(t *testing.T) { } } - // 记录原始容量 + // Record the original capacity originalCap := cap(stream.dataChan) - // 直接调用扩容函数 + // Directly call the expansion function stream.expandDataChannel() - // 验证容量是否增加 + // Verify whether capacity has increased newCap := cap(stream.dataChan) assert.Greater(t, newCap, originalCap, "Channel capacity should increase after expansion") - // 验证数据是否正确迁移 + // Verify that data migration is correct assert.Equal(t, 9, len(stream.dataChan), "All data should be migrated to new channel") - // 测试并发扩容保护 + // Testing concurrent expansion protection var wg sync.WaitGroup for i := 0; i < 3; i++ { wg.Add(1) @@ -924,7 +924,7 @@ func TestExpandDataChannelDirectly(t *testing.T) { wg.Wait() } -// TestExpandDataChannelBelowThreshold 测试通道使用率低于阈值时不扩容 +// TestExpandDataChannelBelowThreshold Does not expand when the test channel's usage falls below the threshold func TestExpandDataChannelBelowThreshold(t *testing.T) { config := types.Config{ SimpleFields: []string{"test"}, @@ -940,22 +940,22 @@ func TestExpandDataChannelBelowThreshold(t *testing.T) { require.NoError(t, err) defer stream.Stop() - // 只填充少量数据(低于80%阈值) + // Fill only a small amount of data (below the 80% threshold) for i := 0; i < 3; i++ { stream.dataChan <- map[string]any{"test": i} } originalCap := cap(stream.dataChan) - // 调用扩容函数 + // Call the expansion function stream.expandDataChannel() - // 验证容量没有变化 + // The verification capacity remains unchanged newCap := cap(stream.dataChan) assert.Equal(t, originalCap, newCap, "Channel capacity should not change when below threshold") } -// TestStreamConfigErrorHandlingEnhanced 测试流配置错误处理增强版 +// TestStreamConfigErrorHandlingEnhanced Teststream Configuration Error Handling Enhanced Edition func TestStreamConfigErrorHandlingEnhanced(t *testing.T) { tests := []struct { name string @@ -1036,7 +1036,7 @@ func TestStreamConfigErrorHandlingEnhanced(t *testing.T) { } } -// TestStreamDataValidationEnhanced 测试流数据验证增强版 +// TestStreamDataValidationEnhanced TestStreamDataValidationEnhanced Version func TestStreamDataValidationEnhanced(t *testing.T) { config := types.NewConfig() config.PerformanceConfig = types.DefaultPerformanceConfig() @@ -1067,7 +1067,7 @@ func TestStreamDataValidationEnhanced(t *testing.T) { "timestamp": "invalid", "value": 1, }, - expectError: false, // 由处理器处理,不直接报错 + expectError: false, // Handled by the processor without directly reporting errors }, { name: "负数值", @@ -1080,9 +1080,9 @@ func TestStreamDataValidationEnhanced(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - // 这里简化测试,只验证数据结构 + // Here, testing is simplified and only data structures are verified if tt.data != nil && len(tt.data) == 0 { - // 空数据测试 + // Empty data testing } }) } @@ -1090,7 +1090,7 @@ func TestStreamDataValidationEnhanced(t *testing.T) { _ = stream } -// TestStreamMemoryPressureEnhanced 测试内存压力场景增强版 +// TestStreamMemoryPressureEnhanced Memory Pressure Enhanced Scenario Version func TestStreamMemoryPressureEnhanced(t *testing.T) { config := types.NewConfig() config.PerformanceConfig = types.PerformanceConfig{ @@ -1110,7 +1110,7 @@ func TestStreamMemoryPressureEnhanced(t *testing.T) { t.Fatalf("failed to create stream: %v", err) } - // 模拟大量数据 + // Simulating large amounts of data largeData := make([]map[string]any, 1000) for i := 0; i < 1000; i++ { largeData[i] = map[string]any{ @@ -1123,7 +1123,7 @@ func TestStreamMemoryPressureEnhanced(t *testing.T) { _ = largeData } -// TestStreamConcurrentAccessEnhanced 测试并发访问边界条件增强版 +// TestStreamConcurrentAccessEnhanced tests concurrent access boundary conditions func TestStreamConcurrentAccessEnhanced(t *testing.T) { config := types.NewConfig() config.PerformanceConfig = types.DefaultPerformanceConfig() @@ -1133,22 +1133,22 @@ func TestStreamConcurrentAccessEnhanced(t *testing.T) { t.Fatalf("failed to create stream: %v", err) } - // 测试并发关闭 + // Test concurrency shutdown go func() { stream.Stop() }() - // 测试并发启动 + // Test concurrent startup go func() { stream.Start() time.Sleep(100 * time.Millisecond) }() - // 给并发操作一些时间 + // Give concurrent operations some time time.Sleep(10 * time.Millisecond) } -// TestStreamWindowEdgeCasesEnhanced 测试窗口边界情况增强版 +// TestStreamWindowEdgeCasesEnhanced version of the test window boundary situation func TestStreamWindowEdgeCasesEnhanced(t *testing.T) { tests := []struct { name string @@ -1161,7 +1161,7 @@ func TestStreamWindowEdgeCasesEnhanced(t *testing.T) { c := types.NewConfig() c.WindowConfig = types.WindowConfig{ Type: "tumbling", - Params: []any{1 * time.Nanosecond}, // 极小时间窗口 + Params: []any{1 * time.Nanosecond}, // Minimal time window TimeUnit: 1 * time.Nanosecond, } c.NeedWindow = true @@ -1175,7 +1175,7 @@ func TestStreamWindowEdgeCasesEnhanced(t *testing.T) { config: types.Config{ WindowConfig: types.WindowConfig{ Type: "tumbling", - Params: []any{8760 * time.Hour}, // 1年 + Params: []any{8760 * time.Hour}, // 1 year TimeUnit: 8760 * time.Hour, }, NeedWindow: true, @@ -1188,7 +1188,7 @@ func TestStreamWindowEdgeCasesEnhanced(t *testing.T) { config: types.Config{ WindowConfig: types.WindowConfig{ Type: "sliding", - Params: []any{1 * time.Second, 1 * time.Millisecond}, // 很小的滑动间隔 + Params: []any{1 * time.Second, 1 * time.Millisecond}, // Very small sliding intervals TimeUnit: 1 * time.Second, }, NeedWindow: true, @@ -1208,7 +1208,7 @@ func TestStreamWindowEdgeCasesEnhanced(t *testing.T) { } } -// TestStreamUnifiedConfigIntegration 测试Stream和Window统一配置的集成 +// TestStreamUnifiedConfigIntegration: Integration of unified configuration between Stream and Window func TestStreamUnifiedConfigIntegration(t *testing.T) { testCases := []struct { name string @@ -1250,19 +1250,19 @@ func TestStreamUnifiedConfigIntegration(t *testing.T) { require.NoError(t, err) defer s.Stop() - // 验证stream的缓冲区配置 + // Verify the buffer configuration of the stream assert.Equal(t, tc.performanceConfig.BufferConfig.DataChannelSize, cap(s.dataChan), "数据通道大小不匹配") assert.Equal(t, tc.performanceConfig.BufferConfig.ResultChannelSize, cap(s.resultChan), "结果通道大小不匹配") - // 验证窗口创建成功 + // Verification window created successfully assert.NotNil(t, s.Window, "窗口应该被创建") }) } } -// TestStreamUnifiedConfigPerformanceImpact 测试统一配置对Stream性能的影响 +// TestStreamUnifiedConfigPerformanceImpact tests the impact of unified configuration on Stream performance func TestStreamUnifiedConfigPerformanceImpact(t *testing.T) { configs := map[string]types.PerformanceConfig{ "默认配置": types.DefaultPerformanceConfig(), @@ -1290,7 +1290,7 @@ func TestStreamUnifiedConfigPerformanceImpact(t *testing.T) { go s.Start() - // 发送测试数据并测量性能 + // Send test data and measure performance dataCount := 1000 startTime := time.Now() @@ -1302,21 +1302,21 @@ func TestStreamUnifiedConfigPerformanceImpact(t *testing.T) { select { case s.dataChan <- data: - // 成功发送 + // Successfully sent case <-time.After(100 * time.Millisecond): - // 发送超时 - t.Logf("第%d条数据发送超时", i) + // Send timeout + t.Logf("%d Data transmission timeout", i) break } } processingTime := time.Since(startTime) - t.Logf("%s 处理%d条数据耗时: %v", name, dataCount, processingTime) + t.Logf("%s Time to process %d data entries: %v", name, dataCount, processingTime) - // 等待一些结果 + // Waiting for some results time.Sleep(1500 * time.Millisecond) - // 检查结果 + // Inspection results resultCount := 0 for { select { @@ -1327,12 +1327,12 @@ func TestStreamUnifiedConfigPerformanceImpact(t *testing.T) { } } done: - // 性能测试主要关注处理时间,结果数量可能因窗口触发时机而变化 + // Performance testing mainly focuses on processing time, and the number of results may vary depending on the window trigger timing }) } } -// TestStreamUnifiedConfigErrorHandling 测试统一配置的错误处理 +// TestStreamUnifiedConfigErrorHandling tests the error handling of the unified configuration func TestStreamUnifiedConfigErrorHandling(t *testing.T) { tests := []struct { name string @@ -1407,9 +1407,9 @@ func TestStreamUnifiedConfigErrorHandling(t *testing.T) { } } -// TestStreamUnifiedConfigCompatibility 测试统一配置的兼容性 +// TestStreamUnifiedConfigCompatibility: Test the compatibility of the unified configuration func TestStreamUnifiedConfigCompatibility(t *testing.T) { - // 测试新的统一配置 + // Test the new unified configuration newConfig := types.Config{ NeedWindow: false, SelectFields: map[string]aggregator.AggregateType{ @@ -1422,11 +1422,11 @@ func TestStreamUnifiedConfigCompatibility(t *testing.T) { require.NoError(t, err) defer s1.Stop() - // 验证新配置生效 + // Verify that the new configuration is effective expectedDataSize := types.HighPerformanceConfig().BufferConfig.DataChannelSize assert.Equal(t, expectedDataSize, cap(s1.dataChan), "高性能配置的数据通道大小不匹配") - // 测试默认配置 + // Test the default configuration defaultConfig := types.Config{ NeedWindow: false, SelectFields: map[string]aggregator.AggregateType{ @@ -1439,7 +1439,7 @@ func TestStreamUnifiedConfigCompatibility(t *testing.T) { require.NoError(t, err) defer s2.Stop() - // 验证默认配置 + // Verify the default configuration expectedDefaultSize := types.DefaultPerformanceConfig().BufferConfig.DataChannelSize assert.Equal(t, expectedDefaultSize, cap(s2.dataChan), "默认配置的数据通道大小不匹配") @@ -1480,7 +1480,7 @@ func TestDataHandlerEnhanced(t *testing.T) { stream.Start() - // 快速发送大量数据 + // Rapidly transmit large amounts of data for i := 0; i < tt.dataCount; i++ { stream.Emit(map[string]any{"value": i}) } @@ -1491,17 +1491,17 @@ func TestDataHandlerEnhanced(t *testing.T) { droppedCount := stats[DroppedCount] if tt.expectedDrops { - // 在高负载下可能会有丢弃 - t.Logf("%s: 输入 %d, 丢弃 %d", tt.name, stats[InputCount], droppedCount) + // There may be discard under heavy loads + t.Logf("%s: Enter %d and discard %d", tt.name, stats[InputCount], droppedCount) } else { - // 高性能配置应该能处理所有数据 + // High-performance configurations should be able to handle all the data assert.Equal(t, int64(0), droppedCount, "高性能配置不应该丢弃数据") } }) } } -// TestResultHandlerEnhanced 测试结果处理器增强版 +// TestResultHandlerEnhanced Enhanced test result processor func TestResultHandlerEnhanced(t *testing.T) { config := types.Config{ NeedWindow: false, @@ -1512,7 +1512,7 @@ func TestResultHandlerEnhanced(t *testing.T) { require.NoError(t, err) defer stream.Stop() - // 测试Sink功能 + // Test the Sink function var mu sync.Mutex var receivedResults []any @@ -1524,7 +1524,7 @@ func TestResultHandlerEnhanced(t *testing.T) { stream.Start() - // 发送测试数据 + // Send test data testData := []map[string]any{ {"value": 1}, {"value": 2}, @@ -1537,13 +1537,13 @@ func TestResultHandlerEnhanced(t *testing.T) { time.Sleep(100 * time.Millisecond) - // 验证结果 + // Verify the results mu.Lock() defer mu.Unlock() assert.GreaterOrEqual(t, len(receivedResults), len(testData), "应该接收到所有结果") - // 验证结果格式 + // Verification result format for _, result := range receivedResults { assert.IsType(t, []map[string]any{}, result, "结果应该是map切片类型") resultSlice := result.([]map[string]any) @@ -1551,7 +1551,7 @@ func TestResultHandlerEnhanced(t *testing.T) { } } -// TestPerformanceConfigurationsEnhanced 测试不同性能配置的效果增强版 +// TestPerformanceConfigurationsEnhancedEnhanced version of the effect for testing different performance configurations func TestPerformanceConfigurationsEnhanced(t *testing.T) { configs := map[string]types.PerformanceConfig{ "Default": types.DefaultPerformanceConfig(), @@ -1571,14 +1571,14 @@ func TestPerformanceConfigurationsEnhanced(t *testing.T) { require.NoError(t, err) defer stream.Stop() - // 验证缓冲区大小 + // Verify the buffer size assert.Equal(t, perfConfig.BufferConfig.DataChannelSize, cap(stream.dataChan)) assert.Equal(t, perfConfig.BufferConfig.ResultChannelSize, cap(stream.resultChan)) - // 验证工作池配置 + // Verify the work pool configuration assert.Equal(t, perfConfig.WorkerConfig.SinkPoolSize, cap(stream.sinkWorkerPool)) - //t.Logf("%s配置: 数据通道=%d, 结果通道=%d, Sink池=%d", + //t.Logf("%s configuration: Data channel = %d, result channel = %d, Sink pool = %d", // name, // cap(stream.dataChan), // cap(stream.resultChan), @@ -1587,13 +1587,13 @@ func TestPerformanceConfigurationsEnhanced(t *testing.T) { } } -// TestNewStreamFactory 测试流工厂创建 +// TestNewStreamFactory Creates the StreamFactory func TestNewStreamFactory(t *testing.T) { factory := NewStreamFactory() assert.NotNil(t, factory) } -// TestStreamFactory_CreateStream 测试创建默认配置的流 +// TestStreamFactory_CreateStream Test the creation of a default configured stream func TestStreamFactory_CreateStream(t *testing.T) { factory := NewStreamFactory() config := types.Config{ @@ -1609,11 +1609,11 @@ func TestStreamFactory_CreateStream(t *testing.T) { } }() - // 验证默认性能配置已应用 + // Verify that the default performance configuration has been applied assert.NotEqual(t, types.PerformanceConfig{}, stream.config.PerformanceConfig) } -// TestStreamFactory_CreateHighPerformanceStream 测试创建高性能流 +// TestStreamFactory_CreateHighPerformanceStream Test to create high-performance streams func TestStreamFactory_CreateHighPerformanceStream(t *testing.T) { factory := NewStreamFactory() config := types.Config{ @@ -1629,12 +1629,12 @@ func TestStreamFactory_CreateHighPerformanceStream(t *testing.T) { } }() - // 验证高性能配置 + // Verify high-performance configurations expectedConfig := types.HighPerformanceConfig() assert.Equal(t, expectedConfig, stream.config.PerformanceConfig) } -// TestStreamFactory_CreateLowLatencyStream 测试创建低延迟流 +// TestStreamFactory_CreateLowLatencyStream Test the creation of low-latency streams func TestStreamFactory_CreateLowLatencyStream(t *testing.T) { factory := NewStreamFactory() config := types.Config{ @@ -1650,12 +1650,12 @@ func TestStreamFactory_CreateLowLatencyStream(t *testing.T) { } }() - // 验证低延迟配置 + // Verify low-latency configurations expectedConfig := types.LowLatencyConfig() assert.Equal(t, expectedConfig, stream.config.PerformanceConfig) } -// TestStreamFactory_CreateCustomPerformanceStream 测试创建自定义性能配置流 +// TestStreamFactory_CreateCustomPerformanceStream Test to create custom performance configuration streams func TestStreamFactory_CreateCustomPerformanceStream(t *testing.T) { factory := NewStreamFactory() config := types.Config{ @@ -1688,11 +1688,11 @@ func TestStreamFactory_CreateCustomPerformanceStream(t *testing.T) { } }() - // 验证自定义配置 + // Verify custom configurations assert.Equal(t, customPerfConfig, stream.config.PerformanceConfig) } -// TestStreamFactory_CreateStreamWithWindow 测试创建带窗口的流 +// TestStreamFactory_CreateStreamWithWindow Test the creation of a stream with a window func TestStreamFactory_CreateStreamWithWindow(t *testing.T) { factory := NewStreamFactory() config := types.Config{ @@ -1715,7 +1715,7 @@ func TestStreamFactory_CreateStreamWithWindow(t *testing.T) { }() } -// TestStreamFactory_CreateStreamWithInvalidStrategy 测试创建无效策略的流 +// TestStreamFactory_CreateStreamWithInvalidStrategy Test the flow that creates invalid policies func TestStreamFactory_CreateStreamWithInvalidStrategy(t *testing.T) { factory := NewStreamFactory() config := types.Config{ @@ -1728,12 +1728,12 @@ func TestStreamFactory_CreateStreamWithInvalidStrategy(t *testing.T) { } _, err := factory.CreateStream(config) - // 应该报错,因为策略无效 + // An error should be reported because the strategy is ineffective require.Error(t, err) assert.Contains(t, err.Error(), "invalid overflow strategy") } -// TestStreamFactory_CreateWindow 测试窗口创建 +// TestStreamFactory_CreateWindow Create test window func TestStreamFactory_CreateWindow(t *testing.T) { factory := NewStreamFactory() config := types.Config{ @@ -1749,7 +1749,7 @@ func TestStreamFactory_CreateWindow(t *testing.T) { assert.NotNil(t, win) } -// TestStreamFactory_CreateStreamInstance 测试流实例创建 +// TestStreamFactory_CreateStreamInstance Create test stream instances func TestStreamFactory_CreateStreamInstance(t *testing.T) { factory := NewStreamFactory() config := types.Config{ @@ -1766,14 +1766,14 @@ func TestStreamFactory_CreateStreamInstance(t *testing.T) { assert.Equal(t, config, stream.config) } -// TestStreamFactory_Performance 测试工厂性能 +// TestStreamFactory_Performance Testing plant performance func TestStreamFactory_Performance(t *testing.T) { factory := NewStreamFactory() config := types.Config{ SimpleFields: []string{"name", "age"}, } - // 创建多个流实例,验证工厂性能 + // Create multiple stream instances to verify plant performance streams := make([]*Stream, 10) for i := 0; i < 10; i++ { stream, err := factory.CreateStream(config) @@ -1781,13 +1781,13 @@ func TestStreamFactory_Performance(t *testing.T) { streams[i] = stream } - // 清理 + // Cleanup for _, stream := range streams { stream.Stop() } } -// TestStreamFactory_ConcurrentCreation 测试并发创建流 +// TestStreamFactory_ConcurrentCreation Test concurrency to create streams func TestStreamFactory_ConcurrentCreation(t *testing.T) { factory := NewStreamFactory() config := types.Config{ @@ -1799,7 +1799,7 @@ func TestStreamFactory_ConcurrentCreation(t *testing.T) { errors := make([]error, numGoroutines) done := make(chan struct{}) - // 并发创建流 + // Simultaneously launching and creating streams for i := 0; i < numGoroutines; i++ { go func(index int) { stream, err := factory.CreateStream(config) @@ -1809,12 +1809,12 @@ func TestStreamFactory_ConcurrentCreation(t *testing.T) { }(i) } - // 等待所有goroutine完成 + // Wait for all goroutines to complete for i := 0; i < numGoroutines; i++ { <-done } - // 验证结果 + // Verify the results for i := 0; i < numGoroutines; i++ { assert.NoError(t, errors[i]) assert.NotNil(t, streams[i]) diff --git a/stream/stream_window_test.go b/stream/stream_window_test.go index c93baa5..9038c58 100644 --- a/stream/stream_window_test.go +++ b/stream/stream_window_test.go @@ -11,7 +11,7 @@ import ( "github.com/stretchr/testify/require" ) -// TestWindowSlotAggregation 测试窗口时间槽聚合 +// TestWindowSlotAggregation: Aggregation of test window time slots func TestWindowSlotAggregation(t *testing.T) { config := types.Config{ WindowConfig: types.WindowConfig{ @@ -35,7 +35,7 @@ func TestWindowSlotAggregation(t *testing.T) { strm.Start() - // 使用固定时间戳的测试数据 + // Test data using fixed timestamps baseTime := time.Date(2025, 4, 7, 16, 46, 0, 0, time.UTC) testData := []map[string]any{ map[string]any{"device": "aa", "temperature": 25.0, "humidity": 60, "ts": baseTime}, @@ -47,13 +47,13 @@ func TestWindowSlotAggregation(t *testing.T) { strm.Emit(data) } - // 捕获结果 + // Capture the results resultChan := make(chan any) strm.AddSink(func(result []map[string]any) { resultChan <- result }) - // 等待窗口触发 + // Wait for the window to trigger time.Sleep(3 * time.Second) select { @@ -98,7 +98,7 @@ func TestWindowSlotAggregation(t *testing.T) { } } -// TestWindowTypes 测试不同类型的窗口 +// TestWindowTypes tests different types of windows func TestWindowTypes(t *testing.T) { tests := []struct { name string @@ -168,7 +168,7 @@ func TestWindowTypes(t *testing.T) { } } -// TestAggregationTypes 测试不同的聚合类型 +// TestAggregationTypes tests different aggregation types func TestAggregationTypes(t *testing.T) { tests := []struct { name string @@ -211,7 +211,7 @@ func TestAggregationTypes(t *testing.T) { stream.Start() - // 发送测试数据 + // Send test data for _, value := range tt.testData { data := map[string]any{ "group": "test", @@ -220,7 +220,7 @@ func TestAggregationTypes(t *testing.T) { stream.Emit(data) } - // 等待窗口关闭 + // Wait for the window to close time.Sleep(700 * time.Millisecond) select { diff --git a/stream/table_store.go b/stream/table_store.go index 2b4582d..f199eae 100644 --- a/stream/table_store.go +++ b/stream/table_store.go @@ -110,11 +110,11 @@ func encodeOne(v any) string { if v == nil { return "" } - // 数值按 SQL 语义归一:1(int)/1.0(float64)/1(uint) 视作相等。否则 JSON 流解码 - // 出的 float64 键与类型化维度表的 int 键永不匹配,INNER JOIN 静默丢行。 + // Values are unified semantically in SQL: 1(int)/1.0(float64)/1(uint) are considered equal. Otherwise, JSON stream is decoded + // The float64 key that appears never matches the int key in the typified dimension table, and INNER JOIN silently drops the line. if f, ok := numericKeyFloat(v); ok { if f == 0 { - f = 0 // 归一 -0.0 → 0 + f = 0 // Normalization -0.0 → 0 } return "n:" + strconv.FormatFloat(f, 'f', -1, 64) } @@ -124,11 +124,11 @@ func encodeOne(v any) string { case bool: return "b:" + strconv.FormatBool(x) } - // 复合/未知类型仍按类型标签隔离,避免误匹配。 + // Composite or unknown types are still isolated by type tags to prevent mismatches. return fmt.Sprintf("%T:%v", v, v) } -// numericKeyFloat 返回数值类型的 float64 表示;非数值返回 ok=false。 +// numericKeyFloat returns float64 of numeric type; Non-numeric returns ok=false. func numericKeyFloat(v any) (float64, bool) { switch x := v.(type) { case float64: diff --git a/stream/table_store_test.go b/stream/table_store_test.go index 69a55fb..74018a9 100644 --- a/stream/table_store_test.go +++ b/stream/table_store_test.go @@ -4,13 +4,13 @@ import ( "testing" ) -// JOIN 键必须按 SQL 数值语义归一:JSON 流解码出的 float64 与类型化维度表的 int 同值 -// 必须匹配,否则 INNER JOIN 静默丢行;同时 string/bool/nil 不得误匹配。 +// The JOIN key must be unified by SQL numeric semantics: the float64 decoded by the JSON stream corresponds to the int value of the typed dimension table +// Must match; otherwise, INNER JOIN silently drops lines; At the same time, string/bool/nil must not be mismatched. func TestEncodeKey_NumericNormalization(t *testing.T) { cases := []struct { name string a, b any - want bool // 期望二者编码是否相等 + want bool // Expect the two codes to be equal }{ {"int vs float64 same value", int(1), float64(1), true}, {"int vs int64 same value", int(1), int64(1), true}, @@ -31,14 +31,14 @@ func TestEncodeKey_NumericNormalization(t *testing.T) { c.name, c.a, c.a, encodeKey(c.a), c.b, c.b, encodeKey(c.b), c.want) } } - // 复合键:各分量分别归一 + // Composite bond: Each component is unified separately if encodeKey([]any{int(1), "a"}) != encodeKey([]any{float64(1), "a"}) { t.Error("composite key: int/float64 segment should normalize") } } func mathNegZero() float64 { - // 返回 -0.0,验证它与 0.0 归一到同一 key。 + // Return -0.0 to verify that it is normalized to the same key as 0.0. var neg float64 return -neg } diff --git a/streamsql.go b/streamsql.go index b3a0bd7..0d5fc17 100644 --- a/streamsql.go +++ b/streamsql.go @@ -59,7 +59,7 @@ type Streamsql struct { schemaValidator *schema.Schema schemaDropped int64 - // 分析函数 PARTITION 分区数上限(≤0 用默认)。由 WithAnalyticMaxPartitions 设置。 + // The PARTITION analysis function sets the maximum number of partitions (≤0 is the default). Configured by WithAnalyticMaxPartitions. analyticMaxPartitions int } @@ -156,7 +156,7 @@ func (s *Streamsql) Execute(sql string) error { // Inject the per-instance logger into the stream pipeline. config.Logger = s.log - // 分析函数分区上限(≤0 时引擎用默认值)。 + // Upper limit of the parser function partition (the engine uses the default value when ≤0). config.AnalyticMaxPartitions = s.analyticMaxPartitions // Create stream processor based on performance mode diff --git a/streamsql_benchmark_test.go b/streamsql_benchmark_test.go index f5506c0..e4dcf26 100644 --- a/streamsql_benchmark_test.go +++ b/streamsql_benchmark_test.go @@ -8,7 +8,7 @@ import ( "time" ) -// BenchmarkStreamSQL StreamSQL基准测试 +// BenchmarkStreamSQL StreamSQL benchmarking func BenchmarkStreamSQL(b *testing.B) { tests := []struct { name string @@ -34,13 +34,13 @@ func BenchmarkStreamSQL(b *testing.B) { for _, tt := range tests { b.Run(tt.name, func(b *testing.B) { - // 使用默认配置 + // Use the default configuration ssql := New() defer ssql.Stop() err := ssql.Execute(tt.sql) if err != nil { - b.Fatalf("SQL执行失败: %v", err) + b.Fatalf("SQL execution failure: %v", err) } var resultCount int64 @@ -48,7 +48,7 @@ func BenchmarkStreamSQL(b *testing.B) { atomic.AddInt64(&resultCount, 1) }) - // 异步消费结果防止阻塞 + // Asynchronous consumption results prevent blockages ctx, cancel := context.WithCancel(context.Background()) defer cancel() @@ -62,13 +62,13 @@ func BenchmarkStreamSQL(b *testing.B) { } }() - // 生成测试数据 + // Generate test data testData := generateOptimizedTestData(5) ssql.Stream().ResetStats() b.ResetTimer() - // 执行基准测试 + // Benchmark the test start := time.Now() for i := 0; i < b.N; i++ { ssql.Emit(testData[i%len(testData)]) @@ -77,37 +77,37 @@ func BenchmarkStreamSQL(b *testing.B) { b.StopTimer() - // 等待处理完成 + // Wait for processing to complete time.Sleep(tt.waitTime) cancel() - // 获取统计信息 + // Get statistics stats := ssql.Stream().GetStats() results := atomic.LoadInt64(&resultCount) - // 计算核心性能指标 + // Calculate core performance metrics throughput := float64(b.N) / inputDuration.Seconds() processedCount := stats["output_count"] droppedCount := stats["dropped_count"] processRate := float64(processedCount) / float64(b.N) * 100 dropRate := float64(droppedCount) / float64(b.N) * 100 - // 报告指标 + // Reporting indicators b.ReportMetric(throughput, "ops/sec") b.ReportMetric(processRate, "process_rate_%") b.ReportMetric(dropRate, "drop_rate_%") b.ReportMetric(float64(results), "results") - // 输出可读的性能报告 - b.Logf("性能报告 - %s:", tt.name) - b.Logf(" 吞吐量: %.0f ops/sec (%.1f万 ops/sec)", throughput, throughput/10000) - b.Logf(" 处理率: %.1f%%, 丢弃率: %.2f%%", processRate, dropRate) - b.Logf(" 结果数: %d", results) + // Output readable performance reports + b.Logf("Performance Report - %s:", tt.name) + b.Logf("Throughput: %.0f ops/sec (%.1f million ops/sec)", throughput, throughput/10000) + b.Logf("Processing rate: %.1f%%, Disposal rate: %.2f%%", processRate, dropRate) + b.Logf("Number of results: %d", results) }) } } -// BenchmarkConfigurationOptimized 优化后的配置对比基准测试 +// BenchmarkConfigurationOptimized configuration comparison benchmark func BenchmarkConfigurationOptimized(b *testing.B) { configs := []struct { name string @@ -142,7 +142,7 @@ func BenchmarkConfigurationOptimized(b *testing.B) { err := ssql.Execute(sql) if err != nil { - b.Fatalf("SQL执行失败: %v", err) + b.Fatalf("SQL execution failure: %v", err) } var resultCount int64 @@ -150,7 +150,7 @@ func BenchmarkConfigurationOptimized(b *testing.B) { atomic.AddInt64(&resultCount, 1) }) - // 异步消费结果 + // Asynchronous consumption results ctx, cancel := context.WithCancel(context.Background()) defer cancel() @@ -180,7 +180,7 @@ func BenchmarkConfigurationOptimized(b *testing.B) { time.Sleep(100 * time.Millisecond) cancel() - // 获取详细统计 + // Get detailed statistics detailedStats := ssql.Stream().GetDetailedStats() results := atomic.LoadInt64(&resultCount) @@ -192,15 +192,15 @@ func BenchmarkConfigurationOptimized(b *testing.B) { b.ReportMetric(processRate, "process_rate_%") b.ReportMetric(dropRate, "drop_rate_%") - b.Logf("%s配置性能:", config.name) - b.Logf(" 吞吐量: %.0f ops/sec (%.1f万 ops/sec)", throughput, throughput/10000) - b.Logf(" 处理率: %.1f%%, 丢弃率: %.2f%%", processRate, dropRate) - b.Logf(" 结果数: %d", results) + b.Logf("%s Configuration Performance:", config.name) + b.Logf("Throughput: %.0f ops/sec (%.1f million ops/sec)", throughput, throughput/10000) + b.Logf("Processing rate: %.1f%%, Disposal rate: %.2f%%", processRate, dropRate) + b.Logf("Number of results: %d", results) }) } } -// BenchmarkPureInputOptimized 优化后的纯输入性能测试 +// BenchmarkPureInputOptimized: Optimized pure input performance testing func BenchmarkPureInputOptimized(b *testing.B) { ssql := New(WithHighPerformance()) defer ssql.Stop() @@ -211,7 +211,7 @@ func BenchmarkPureInputOptimized(b *testing.B) { b.Fatal(err) } - // 启动结果消费者 + // Launch results for consumers ctx, cancel := context.WithCancel(context.Background()) defer cancel() @@ -225,7 +225,7 @@ func BenchmarkPureInputOptimized(b *testing.B) { } }() - // 预生成数据 + // Pre-generated data data := map[string]any{ "deviceId": "device1", "temperature": 25.0, @@ -243,10 +243,10 @@ func BenchmarkPureInputOptimized(b *testing.B) { throughput := float64(b.N) / duration.Seconds() b.ReportMetric(throughput, "pure_ops/sec") - b.Logf("纯输入性能: %.0f ops/sec (%.1f万 ops/sec)", throughput, throughput/10000) + b.Logf("Pure input performance: %.0f ops/sec (%.1f million ops/sec)", throughput, throughput/10000) } -// BenchmarkPostAggregationPerformance 后聚合性能基准测试 +// BenchmarkPostAggregationPerformance followed by aggregated performance benchmarking func BenchmarkPostAggregationPerformance(b *testing.B) { tests := []struct { name string @@ -273,7 +273,7 @@ func BenchmarkPostAggregationPerformance(b *testing.B) { err := ssql.Execute(tt.sql) if err != nil { - b.Fatalf("SQL执行失败: %v", err) + b.Fatalf("SQL execution failure: %v", err) } var resultCount int64 @@ -324,15 +324,15 @@ func BenchmarkPostAggregationPerformance(b *testing.B) { b.ReportMetric(dropRate, "drop_rate_%") b.ReportMetric(float64(results), "results") - b.Logf("%s性能:", tt.name) - b.Logf(" 吞吐量: %.0f ops/sec (%.1f万 ops/sec)", throughput, throughput/10000) - b.Logf(" 处理率: %.1f%%, 丢弃率: %.2f%%", processRate, dropRate) - b.Logf(" 结果数: %d", results) + b.Logf("%s Performance:", tt.name) + b.Logf("Throughput: %.0f ops/sec (%.1f million ops/sec)", throughput, throughput/10000) + b.Logf("Processing rate: %.1f%%, Disposal rate: %.2f%%", processRate, dropRate) + b.Logf("Number of results: %d", results) }) } } -// generateOptimizedTestData 生成优化的测试数据 +// generateOptimizedTestData generates optimized test data func generateOptimizedTestData(count int) []map[string]any { data := make([]map[string]any, count) devices := []string{"device1", "device2", "device3", "device4", "device5"} @@ -340,7 +340,7 @@ func generateOptimizedTestData(count int) []map[string]any { for i := 0; i < count; i++ { data[i] = map[string]any{ "deviceId": devices[rand.Intn(len(devices))], - "temperature": 15.0 + rand.Float64()*20, // 15-35度 + "temperature": 15.0 + rand.Float64()*20, // 15-35 degrees "humidity": 30.0 + rand.Float64()*40, // 30-70% "timestamp": time.Now().UnixNano(), } @@ -348,7 +348,7 @@ func generateOptimizedTestData(count int) []map[string]any { return data } -// BenchmarkMemoryEfficiency 内存效率基准测试 +// BenchmarkMemoryEfficiency Memory Efficiency Benchmark func BenchmarkMemoryEfficiency(b *testing.B) { configs := []struct { name string @@ -383,7 +383,7 @@ func BenchmarkMemoryEfficiency(b *testing.B) { err := ssql.Execute(sql) if err != nil { - b.Fatalf("SQL执行失败: %v", err) + b.Fatalf("SQL execution failure: %v", err) } var resultCount int64 @@ -434,13 +434,13 @@ func BenchmarkMemoryEfficiency(b *testing.B) { b.ReportMetric(float64(results), "results") basicStats := detailedStats["basic_stats"].(map[string]int64) - b.Logf("%s配置效率:", config.name) - b.Logf(" 缓冲区: 数据%d/结果%d/Sink%d", + b.Logf("%s Configuration Efficiency:", config.name) + b.Logf("Buffer: Data %d / Result %d / Sink%d", basicStats["data_chan_cap"], basicStats["result_chan_cap"], basicStats["sink_pool_cap"]) - b.Logf(" 吞吐量: %.0f ops/sec, 处理率: %.1f%%, 结果数: %d", throughput, processRate, results) - b.Logf(" 通道使用率: 数据%.1f%%, 结果%.1f%%", dataChanUsage, resultChanUsage) + b.Logf("Throughput: %.0f ops/sec, Throughput: %.1f%%, Number of Results: %d", throughput, processRate, results) + b.Logf("Channel Utilization: Data %.1f%%, results %.1f%%", dataChanUsage, resultChanUsage) }) } } diff --git a/streamsql_stress_test.go b/streamsql_stress_test.go index fc8764d..10c2272 100644 --- a/streamsql_stress_test.go +++ b/streamsql_stress_test.go @@ -147,7 +147,7 @@ func TestStress_MemoryAndThroughput(t *testing.T) { perRowB := float64(peak.TotalAlloc-baseline.TotalAlloc) / float64(produced) t.Logf("[%s] rows=%d producers=%d", sc.name, produced, producers) - t.Logf(" ingest throughput : %.0f rows/sec (%.1f万/s)", throughput, throughput/1e4) + t.Logf("ingest throughput: %.0f rows/sec (%.1f million per s)", throughput, throughput/1e4) t.Logf(" peak heap : %.1f MB", peakHeapMB) t.Logf(" retained after GC : %.2f MB (delta vs baseline)", retainedMB) t.Logf(" total alloc/row : %.0f B", perRowB) diff --git a/streamsql_test.go b/streamsql_test.go index db25394..bc08057 100644 --- a/streamsql_test.go +++ b/streamsql_test.go @@ -16,10 +16,10 @@ package streamsql -// package streamsql 的白盒测试与基准(覆盖/性能/溢出策略/表格打印 + 端到端示例)。 -// 访问非导出字段(performanceMode/customConfig/stream/fieldOrder)与非导出方法 -// (printTableFormat),故必须在 package streamsql 内,不能迁 test/e2e。 -// 纯公开 API 的集成测试见 test/e2e。 +// Package streamsql white-box testing and benchmarking (override/performance/overflow policy/table printing + end-to-end examples). +// Access non-export fields (performanceMode/customConfig/stream/fieldOrder) and non-export methods +// (printTableFormat), so it must be inside package streamsql and cannot transfer test/e2e. +// For fully public API integration testing, see test/e2e. import ( "context" @@ -36,7 +36,7 @@ import ( ) // ---------- coverage ---------- -// TestStreamSQLPerformanceModesExtended 测试不同性能模式的配置 +// TestStreamSQLPerformanceModesExtended tests the configurations of different performance modes func TestStreamSQLPerformanceModesExtended(t *testing.T) { t.Run("default performance mode", func(t *testing.T) { ssql := New() @@ -95,14 +95,14 @@ func TestStreamSQLPerformanceModesExtended(t *testing.T) { }) } -// TestStreamSQLFieldOrder 测试字段顺序保持功能 +// TestStreamSQLFieldOrder Test field order retention function func TestStreamSQLFieldOrder(t *testing.T) { t.Run("field order preservation", func(t *testing.T) { ssql := New() err := ssql.Execute("SELECT name, id, value FROM stream") require.NoError(t, err) - // 验证字段顺序被正确保存 + // Verify that the order of the fields is saved correctly expectedOrder := []string{"name", "id", "value"} assert.Equal(t, expectedOrder, ssql.fieldOrder) ssql.Stop() @@ -113,27 +113,27 @@ func TestStreamSQLFieldOrder(t *testing.T) { err := ssql.Execute("SELECT name as device_name, id as device_id FROM stream") require.NoError(t, err) - // 验证别名字段顺序 + // Verify the order of alias fields expectedOrder := []string{"device_name", "device_id"} assert.Equal(t, expectedOrder, ssql.fieldOrder) ssql.Stop() }) } -// TestStreamSQLPrintTableFormat 测试表格打印功能 +// TestStreamSQLPrintTableFormat tests the table printing function func TestStreamSQLPrintTableFormat(t *testing.T) { t.Run("print table format with data", func(t *testing.T) { ssql := New() err := ssql.Execute("SELECT id, name FROM stream") require.NoError(t, err) - // 测试 printTableFormat 方法 + // Test the printTableFormat method testResults := []map[string]any{ {"id": 1, "name": "test1"}, {"id": 2, "name": "test2"}, } - // 这个方法主要是打印输出,我们确保它不会panic + // This method mainly involves printing output, and we make sure it doesn't panic assert.NotPanics(t, func() { ssql.printTableFormat(testResults) }) @@ -145,7 +145,7 @@ func TestStreamSQLPrintTableFormat(t *testing.T) { err := ssql.Execute("SELECT id FROM stream") require.NoError(t, err) - // 测试空数据 + // Test empty data emptyResults := []map[string]any{} assert.NotPanics(t, func() { ssql.printTableFormat(emptyResults) @@ -158,7 +158,7 @@ func TestStreamSQLPrintTableFormat(t *testing.T) { err := ssql.Execute("SELECT id FROM stream") require.NoError(t, err) - // 清空字段顺序 + // Clear the field order ssql.fieldOrder = nil testResults := []map[string]any{ {"id": 1}, @@ -171,18 +171,18 @@ func TestStreamSQLPrintTableFormat(t *testing.T) { }) } -// TestStreamSQLToChannel 测试通道功能 +// TestStreamSQLToChannel test channel functionality func TestStreamSQLToChannel(t *testing.T) { t.Run("to channel with aggregation query", func(t *testing.T) { ssql := New() err := ssql.Execute("SELECT COUNT(*) FROM stream GROUP BY TumblingWindow('1s')") require.NoError(t, err) - // 获取结果通道 + // Obtain the results channel resultChan := ssql.ToChannel() assert.NotNil(t, resultChan) - // 启动goroutine接收结果 + // Start goroutine to receive results var wg sync.WaitGroup wg.Add(1) var receivedResults [][]map[string]any @@ -202,16 +202,16 @@ func TestStreamSQLToChannel(t *testing.T) { } }() - // 发送一些数据 + // Send some data for i := 0; i < 5; i++ { ssql.Emit(map[string]any{"id": i}) } - // 等待结果 + // Wait for the results wg.Wait() ssql.Stop() - // 验证至少收到了一些结果 + // At least some results have been obtained from the verification assert.GreaterOrEqual(t, len(receivedResults), 0) }) @@ -226,10 +226,10 @@ func TestStreamSQLToChannel(t *testing.T) { }) } -// TestStreamSQLMultipleOptions 测试多个配置选项组合 +// TestStreamSQLMultipleOptions tests multiple configuration option combinations func TestStreamSQLMultipleOptions(t *testing.T) { t.Run("multiple options combination", func(t *testing.T) { - // 组合多个配置选项 + // Combine multiple configuration options ssql := New( WithHighPerformance(), WithDiscardLog(), @@ -242,7 +242,7 @@ func TestStreamSQLMultipleOptions(t *testing.T) { }) t.Run("override performance mode", func(t *testing.T) { - // 后面的选项应该覆盖前面的 + // The later options should override the earlier ones ssql := New( WithHighPerformance(), WithLowLatency(), @@ -255,11 +255,11 @@ func TestStreamSQLMultipleOptions(t *testing.T) { }) } -// TestStreamSQLExecuteErrorHandling 测试Execute方法的错误处理 +// TestStreamSQLExecuteErrorHandling: Error handling of the Execute method func TestStreamSQLExecuteErrorHandling(t *testing.T) { t.Run("stream creation failure simulation", func(t *testing.T) { ssql := New() - // 使用一个可能导致stream创建失败的SQL + // Using an SQL that may cause stream creation failure err := ssql.Execute("SELECT invalid_function() FROM test_stream") require.NotNil(t, err) require.Contains(t, err.Error(), "function") @@ -268,10 +268,10 @@ func TestStreamSQLExecuteErrorHandling(t *testing.T) { t.Run("filter registration failure", func(t *testing.T) { ssql := New() defer ssql.Stop() - // 使用可能导致过滤器注册失败的SQL + // Use SQL that may cause filter registration failures err := ssql.Execute("SELECT id FROM stream WHERE INVALID_CONDITION") if err != nil { - // 如果有错误,应该包含相关信息 + // If there are errors, relevant information should be included assert.True(t, strings.Contains(err.Error(), "SQL parsing failed") || strings.Contains(err.Error(), "failed to register filter condition") || @@ -280,7 +280,7 @@ func TestStreamSQLExecuteErrorHandling(t *testing.T) { }) } -// TestStreamSQLConcurrentAccess 测试并发访问安全性 +// TestStreamSQLConcurrentAccess tests the security of concurrent access func TestStreamSQLConcurrentAccess(t *testing.T) { t.Run("concurrent emit and stop", func(t *testing.T) { ssql := New() @@ -290,7 +290,7 @@ func TestStreamSQLConcurrentAccess(t *testing.T) { var wg sync.WaitGroup numWorkers := 10 - // 启动多个goroutine并发发送数据 + // Starts multiple goroutines to send data concurrently for i := 0; i < numWorkers; i++ { wg.Add(1) go func(workerID int) { @@ -301,7 +301,7 @@ func TestStreamSQLConcurrentAccess(t *testing.T) { }(i) } - // 等待一段时间后停止 + // After waiting for a while, stop time.Sleep(100 * time.Millisecond) ssql.Stop() @@ -316,12 +316,12 @@ func TestStreamSQLConcurrentAccess(t *testing.T) { var wg sync.WaitGroup numWorkers := 5 - // 并发调用各种方法 + // Concurrent calls to various methods for i := 0; i < numWorkers; i++ { wg.Add(1) go func() { defer wg.Done() - // 这些方法调用应该是安全的 + // These method calls should be safe _ = ssql.GetStats() _ = ssql.GetDetailedStats() _ = ssql.IsAggregationQuery() @@ -336,22 +336,22 @@ func TestStreamSQLConcurrentAccess(t *testing.T) { }) } -// TestStreamSQLEdgeCasesAdditional 测试额外的边界情况 +// TestStreamSQLEdgeCasesAdditional tests for additional boundary cases func TestStreamSQLEdgeCasesAdditional(t *testing.T) { t.Run("execute with different performance modes after creation", func(t *testing.T) { ssql := New() - // 先用默认模式执行 + // Start by running in default mode err := ssql.Execute("SELECT id FROM stream") require.NoError(t, err) ssql.Stop() - // 改变性能模式后再次执行应该失败,因为已经执行过了 + // Executing again after changing the performance mode should fail because it has already been executed ssql.performanceMode = "high_performance" err = ssql.Execute("SELECT name FROM stream") require.Error(t, err) require.Contains(t, err.Error(), "Execute() has already been called") - // 不需要再次调用Stop(),因为第二次Execute失败了 + // There is no need to call Stop() again because the second Execute failed }) t.Run("field order with complex query", func(t *testing.T) { @@ -359,7 +359,7 @@ func TestStreamSQLEdgeCasesAdditional(t *testing.T) { err := ssql.Execute("SELECT COUNT(*) as cnt, AVG(value) as avg_val, deviceId FROM stream GROUP BY deviceId") require.NoError(t, err) - // 验证复杂查询的字段顺序 + // Verify the field order for complex queries expectedOrder := []string{"cnt", "avg_val", "deviceId"} assert.Equal(t, expectedOrder, ssql.fieldOrder) ssql.Stop() @@ -370,10 +370,10 @@ func TestStreamSQLEdgeCasesAdditional(t *testing.T) { err := ssql.Execute("SELECT name, id, value FROM stream") require.NoError(t, err) - // 设置字段顺序 + // Set the order of the fields ssql.fieldOrder = []string{"name", "id", "value"} - // 测试PrintTable方法 + // Test the PrintTable method assert.NotPanics(t, func() { ssql.PrintTable() }) @@ -381,11 +381,11 @@ func TestStreamSQLEdgeCasesAdditional(t *testing.T) { }) } -// TestStreamSQLEmitSync 测试EmitSync方法的各种情况 +// TestStreamSQLEmitSync tests various scenarios for the EmitSync method func TestStreamSQLEmitSync(t *testing.T) { t.Run("emit sync with uninitialized stream", func(t *testing.T) { ssql := New() - // 在没有执行SQL的情况下调用EmitSync + // Call EmitSync without executing the SQL result, err := ssql.EmitSync(map[string]any{"id": 1}) require.Error(t, err) require.Nil(t, result) @@ -397,7 +397,7 @@ func TestStreamSQLEmitSync(t *testing.T) { err := ssql.Execute("SELECT COUNT(*) FROM stream GROUP BY id") require.NoError(t, err) - // 对聚合查询调用EmitSync应该返回错误 + // Calling EmitSync for aggregated queries should return an error result, err := ssql.EmitSync(map[string]any{"id": 1}) require.Error(t, err) require.Nil(t, result) @@ -410,10 +410,10 @@ func TestStreamSQLEmitSync(t *testing.T) { err := ssql.Execute("SELECT id, name FROM stream WHERE id > 0") require.NoError(t, err) - // 对非聚合查询调用EmitSync + // Call EmitSync for non-aggregated queries data := map[string]any{"id": 1, "name": "test"} result, err := ssql.EmitSync(data) - // 根据实际实现,这里可能成功或失败 + // Depending on actual implementation, success or failure may be possible here if err != nil { t.Logf("EmitSync error (expected): %v", err) } else { @@ -423,14 +423,14 @@ func TestStreamSQLEmitSync(t *testing.T) { }) } -// TestStreamSQLCustomPerformanceConfig 测试自定义性能配置 +// TestStreamSQLCustomPerformanceConfig tests custom performance configurations func TestStreamSQLCustomPerformanceConfig(t *testing.T) { t.Run("custom performance config with nil config", func(t *testing.T) { ssql := New() ssql.performanceMode = "custom" - ssql.customConfig = nil // 设置为nil + ssql.customConfig = nil // Set to nil - // 执行SQL时应该回退到默认配置 + // When executing SQL, you should revert to the default configuration err := ssql.Execute("SELECT id FROM stream") require.NoError(t, err) ssql.Stop() @@ -458,7 +458,7 @@ func TestStreamSQLCustomPerformanceConfig(t *testing.T) { }) } -// TestStreamSQLStatsMethods 测试统计信息相关方法 +// TestStreamSQLStatsMethods Methods for testing statistical information func TestStreamSQLStatsMethods(t *testing.T) { t.Run("get stats with uninitialized stream", func(t *testing.T) { ssql := New() @@ -489,18 +489,18 @@ func TestStreamSQLStatsMethods(t *testing.T) { }) t.Run("is aggregation query method", func(t *testing.T) { - // 测试未初始化的情况 + // Test for cases where the initialization is not initialized ssql := New() require.False(t, ssql.IsAggregationQuery()) - // 测试非聚合查询 + // Test non-aggregated queries err := ssql.Execute("SELECT id FROM stream") require.NoError(t, err) isAgg := ssql.IsAggregationQuery() t.Logf("Is aggregation query: %v", isAgg) ssql.Stop() - // 测试聚合查询 + // Test aggregated queries ssql2 := New() err = ssql2.Execute("SELECT COUNT(*) FROM stream GROUP BY id") require.NoError(t, err) @@ -510,11 +510,11 @@ func TestStreamSQLStatsMethods(t *testing.T) { }) } -// TestStreamSQLNilAndEdgeCases 测试空值和边界情况 +// TestStreamSQLNilAndEdgeCases tests the null values and boundary conditions func TestStreamSQLNilAndEdgeCases(t *testing.T) { t.Run("emit with nil stream", func(t *testing.T) { ssql := New() - // 在没有执行SQL的情况下调用Emit + // Calling Emit without executing SQL assert.NotPanics(t, func() { ssql.Emit(map[string]any{"id": 1}) }) @@ -522,7 +522,7 @@ func TestStreamSQLNilAndEdgeCases(t *testing.T) { t.Run("add sink with nil stream", func(t *testing.T) { ssql := New() - // 在没有执行SQL的情况下调用AddSink + // Call AddSink without executing the SQL assert.NotPanics(t, func() { ssql.AddSink(func(results []map[string]any) { t.Log("Sink called") @@ -532,21 +532,21 @@ func TestStreamSQLNilAndEdgeCases(t *testing.T) { t.Run("to channel with nil stream", func(t *testing.T) { ssql := New() - // 在没有执行SQL的情况下调用ToChannel + // Call ToChannel without executing SQL resultChan := ssql.ToChannel() require.Nil(t, resultChan) }) t.Run("stream method with nil stream", func(t *testing.T) { ssql := New() - // 在没有执行SQL的情况下调用Stream + // Call Stream without executing the SQL stream := ssql.Stream() require.Nil(t, stream) }) t.Run("stop with nil stream", func(t *testing.T) { ssql := New() - // 在没有执行SQL的情况下调用Stop + // Call Stop without executing the SQL assert.NotPanics(t, func() { ssql.Stop() }) @@ -556,7 +556,7 @@ func TestStreamSQLNilAndEdgeCases(t *testing.T) { ssql := New() ssql.fieldOrder = []string{"id", "name"} - // 测试空结果的表格打印 + // Test empty results for the table print assert.NotPanics(t, func() { ssql.printTableFormat([]map[string]any{}) }) @@ -570,31 +570,31 @@ func TestStreamSQLNilAndEdgeCases(t *testing.T) { {"id": 1, "name": "test"}, } - // 测试nil字段顺序的表格打印 + // Print a table to test the order of the nil fields assert.NotPanics(t, func() { ssql.printTableFormat(results) }) }) } -// TestStreamSQLComplexScenarios 测试复杂场景 +// TestStreamSQLComplexScenarios tests complex scenarios func TestStreamSQLComplexScenarios(t *testing.T) { t.Run("multiple execute calls", func(t *testing.T) { ssql := New() - // 第一次执行 + // The first time it was executed err := ssql.Execute("SELECT id FROM stream") require.NoError(t, err) ssql.Stop() - // 第二次执行应该失败,因为已经执行过了 + // The second execution should fail because it has already been carried out err = ssql.Execute("SELECT name FROM stream") require.Error(t, err) require.Contains(t, err.Error(), "Execute() has already been called") }) t.Run("performance mode switching", func(t *testing.T) { - // 测试所有性能模式 + // Test all performance modes modes := []string{"default", "high_performance", "low_latency", "zero_data_loss"} for _, mode := range modes { @@ -615,7 +615,7 @@ func TestStreamSQLComplexScenarios(t *testing.T) { err := ssql.Execute("SELECT z, a, m, b FROM stream") require.NoError(t, err) - // 验证字段顺序被正确保存 + // Verify that the order of the fields is saved correctly expectedOrder := []string{"z", "a", "m", "b"} require.Equal(t, expectedOrder, ssql.fieldOrder) ssql.Stop() @@ -688,9 +688,9 @@ func BenchmarkMainPath_NoFilter(b *testing.B) { } // ---------- overflow strategy ---------- -// TestSQLIntegration_StrategyBlock 测试 SQL 集成下的阻塞策略 +// TestSQLIntegration_StrategyBlock Test blocking policies under SQL integration func TestSQLIntegration_StrategyBlock(t *testing.T) { - // 配置:输出缓冲为 1,阻塞策略,超时 100ms + // Configuration: Output buffer is 1, blocking policy, timeout is 100ms ssql := New(WithCustomPerformance(types.PerformanceConfig{ BufferConfig: types.BufferConfig{ DataChannelSize: 100, @@ -703,63 +703,63 @@ func TestSQLIntegration_StrategyBlock(t *testing.T) { AllowDataLoss: true, }, WorkerConfig: types.WorkerConfig{ - SinkPoolSize: 0, // 无缓冲任务队列 - SinkWorkerCount: 1, // 1个 worker + SinkPoolSize: 0, // No buffer task queue + SinkWorkerCount: 1, // 1 worker }, })) defer ssql.Stop() - // SQL: 每条数据触发一次窗口 + // SQL: Each data entry triggers a window once rsql := "SELECT deviceId FROM stream GROUP BY deviceId, CountingWindow(1)" err := ssql.Execute(rsql) require.NoError(t, err) - // 添加同步 Sink 阻塞 Stream 处理,从而反压 Window - // 注意:必须在 Execute 之后添加,因为 Execute 才会创建 stream + // Adds synchronous Sink blocking stream handling, thereby backpressing Windows + // Note: It must be added after Execute, because Execute will create the stream ssql.AddSyncSink(func(results []map[string]any) { time.Sleep(500 * time.Millisecond) }) - // 发送 5 条数据 - // d1: Worker 处理中 (阻塞 500ms) - // d2: Stream 尝试写入 WorkerPool -> 阻塞 (无缓冲) - // d3: Window OutputChan (size 1) -> 填满 - // d4: Window OutputChan 满 -> 尝试写入 -> 阻塞 (Window Add) -> 放入 TriggerChan (size=1) - // d5: Window Add -> TriggerChan 满 -> 阻塞? No, Emit 是异步的? - // Emit 往 dataChan 写. DataProcessor 读 dataChan -> Window.Add. - // Window.Add 往 triggerChan 写. + // Send 5 data entries + // d1: Worker processing (blocking 500ms) + // d2: Stream tries to write to WorkerPool -> block (no buffering) + // d3: Window OutputChan (size 1) -> fill + // d4: Window OutputChan full -> tries to write -> block (Window Add) -> insert TriggerChan (size=1) + // d5: Window Add -> TriggerChan full -> blocking? No, is Emit asynchronous? + // Emit wrote to dataChan. DataProcessor reads dataChan -> Window.Add. + // Window.Add to triggerChan. // - // 修正分析: - // Window.Add 是非阻塞的 (如果 triggerChan 不满). + // Correction Analysis: + // Window.Add is non-blocking (if triggerChan is dissatisfied). // CountingWindow triggerChan size = bufferSize = 1. - // Worker 协程: 从 triggerChan 读 -> 处理 -> sendResult (到 OutputChan). + // Worker coroutine: Read -> from triggerChan and process -> sendResult (to OutputChan). // - // d1: Worker读triggerChan -> OutputChan -> Stream -> WorkerPool -> Worker(busy). - // d2: Worker读triggerChan -> OutputChan -> Stream -> Blocked on WorkerPool. - // 此时 Stream 持有 d2. OutputChan 空. - // Worker 协程 阻塞在 sendResult(d2)? No, Stream 取走了 d2, Stream 阻塞在 dispatch. - // 所以 OutputChan 是空的! + // d1: Worker reads triggerChan -> OutputChan -> Stream -> WorkerPool -> Worker(busy). + // d2: Worker reads triggerChan -> OutputChan -> Stream -> Blocked on WorkerPool. + // At this point, Stream holds d2. OutputChan empty. + // Worker coroutine blocked at sendResult(d2)? No, Stream takes d2, Stream blocks dispatch. + // So OutputChan is empty! // Wait, Stream loop: // result := <-OutputChan. (Stream has d2). // handleResult(d2) -> Blocked. // So OutputChan is empty. - // d3: Worker读triggerChan -> OutputChan (d3). Success. + // d3: Worker reads triggerChan -> OutputChan (d3). Success. // OutputChan has d3. - // d4: Worker读triggerChan -> OutputChan (d4). Blocked (OutputChan full). - // Worker 协程 阻塞在 sendResult(d4). + // d4: Worker reads triggerChan -> OutputChan (d4). Blocked (OutputChan full). + // Worker coroutine blocked at sendResult(d4). // d5: Add -> triggerChan (d5). Success (triggerChan size 1). // d6: Add -> triggerChan (d6). Blocked (triggerChan full). // Add blocks. DataProcessor blocks. Emit succeeds (dataChan). // - // 所以 Window Worker 只有在 sendResult 阻塞时才触发 Drop logic. - // sendResult 只有在 OutputChan 满且超时时才 Drop. + // Therefore, the Window Worker only triggers the drop logic when sendResult is blocked. + // sendResult only drops when OutputChan is full and timed out. // - // d4 阻塞在 sendResult. - // 100ms 后超时 -> Drop d4. - // Worker 继续. + // d4 is blocked in sendResult. + // Timeout after 100ms -> Drop d4. + // Worker continues. // - // 所以 d4 应该是被 Drop 的那个. - // Sent: d1, d2, d3. (d5 在 triggerChan, d6 在 dataChan). + // So d4 should be the one that got dropped. + // Sent: d1, d2, d3. (d5 on triggerChan, d6 on dataChan). // Wait, d5 is in triggerChan, not processed yet. // So Sent = 3. Dropped = 1 (d4). @@ -768,15 +768,15 @@ func TestSQLIntegration_StrategyBlock(t *testing.T) { time.Sleep(10 * time.Millisecond) } - // 等待足够长的时间让 Stream 醒来并处理完,以及 Window 丢弃逻辑执行 + // Wait long enough for the stream to wake up and finish processing, and for the window to discard the logic execution time.Sleep(1000 * time.Millisecond) - // 获取统计信息 - // d1: Stream 处理完 - // d2: Stream 处理完 (Worker 醒来后处理 d2) - // d3: Dropped (Worker 阻塞 -> 超时) - // d4: Dropped (Worker 阻塞 -> 超时) - // d5: Dropped (Worker 阻塞 -> 超时) + // Get statistics + // d1: Stream finishes processing + // d2: Stream finishes processing (Worker wakes up and processes d2) + // d3: Dropped (Worker blocks -> timeout) + // d4: Dropped (Worker blocks -> timeout) + // d5: Dropped (Worker blocks -> timeout) // Total Sent: 2 (d1, d2). // Dropped: 3 (d3, d4, d5). stats := ssql.stream.GetStats() @@ -784,9 +784,9 @@ func TestSQLIntegration_StrategyBlock(t *testing.T) { assert.Equal(t, int64(2), stats["sentCount"], "Should have 2 sent window result") } -// TestSQLIntegration_StrategyDrop 测试 SQL 集成下的丢弃策略 +// TestSQLIntegration_StrategyDrop Test the dropout policy under SQL integration func TestSQLIntegration_StrategyDrop(t *testing.T) { - // 配置:输出缓冲为 1,丢弃策略 + // Configuration: Output buffer is set to 1, discard policy ssql := New(WithCustomPerformance(types.PerformanceConfig{ BufferConfig: types.BufferConfig{ DataChannelSize: 100, @@ -799,50 +799,50 @@ func TestSQLIntegration_StrategyDrop(t *testing.T) { })) defer ssql.Stop() - // SQL: 每条数据触发一次窗口 + // SQL: Each data entry triggers a window once rsql := "SELECT deviceId FROM stream GROUP BY deviceId, CountingWindow(1)" err := ssql.Execute(rsql) require.NoError(t, err) - // 连续发送 3 条数据 + // Send 3 data records consecutively ssql.Emit(map[string]any{"deviceId": "d1"}) ssql.Emit(map[string]any{"deviceId": "d2"}) ssql.Emit(map[string]any{"deviceId": "d3"}) - // 等待处理完成 + // Wait for processing to complete time.Sleep(200 * time.Millisecond) - // 对于 StrategyDrop,它会挤掉旧数据,所以 sentCount 应该持续增加 + // For StrategyDrop, it squeezes out old data, so sentCount should keep increasing stats := ssql.stream.GetStats() - // d1, d2, d3 都会成功发送(虽然 d1, d2 可能被挤掉,但 sendResult 逻辑中挤掉旧的后写入新的算发送成功) + // d1, d2, and d3 will all be successfully sent (although d1 and d2 may be squeezed out, the sendResult logic pushes out the old ones and writes the new ones to count as successful sends). assert.Equal(t, int64(3), stats["sentCount"]) - // 验证最终留在缓冲区的是最后一条数据 (d3) - // 注意:AddSink 会启动 worker 从 OutputChan 读。 - // 为了验证,我们直接从 Window 的 OutputChan 读 + // Verification ultimately remains in the buffer with the last data (d3) + // Note: AddSink will start the worker to read from OutputChan. + // To verify, we read directly from OutputChan in Windows select { case result := <-ssql.stream.Window.OutputChan(): assert.Equal(t, "d3", result[0].Data.(map[string]any)["deviceId"]) case <-time.After(100 * time.Millisecond): - // 如果已经被 AddSink 的 worker 读走了也正常,但由于我们没加 Sink,所以应该在里面 + // If the AddSink worker has already read it, that's normal, but since we didn't add a Sink, it should be inside } } // ---------- table print ---------- -// TestPrintTable 测试PrintTable方法的基本功能 +// TestPrintTable Tests the basic functionality of the PrintTable method func TestPrintTable(t *testing.T) { - // 创建StreamSQL实例并测试PrintTable + // Create a StreamSQL instance and test the PrintTable ssql := New() defer ssql.Stop() err := ssql.Execute("SELECT device, AVG(temperature) as avg_temp FROM stream GROUP BY device, TumblingWindow('2s')") assert.NoError(t, err) - // 使用PrintTable方法(不验证输出内容,只确保不会panic) + // Use the PrintTable method (does not verify the output, only ensures it does not panic) assert.NotPanics(t, func() { ssql.PrintTable() }, "PrintTable方法不应该panic") - // 发送测试数据 + // Send test data testData := []map[string]any{ {"device": "sensor1", "temperature": 25.0}, {"device": "sensor2", "temperature": 30.0}, @@ -852,101 +852,101 @@ func TestPrintTable(t *testing.T) { ssql.Emit(data) } - // 等待窗口触发 + // Wait for the window to trigger time.Sleep(3 * time.Second) } -// TestPrintTableFormat 测试printTableFormat方法处理不同数据类型 +// TestPrintTableFormat: The printTableFormat method handles different data types func TestPrintTableFormat(t *testing.T) { ssql := New() - // 测试不同类型的数据,确保不会panic + // Test different types of data to ensure you don't panic assert.NotPanics(t, func() { - // 测试空切片 + // Test the empty slices ssql.printTableFormat([]map[string]any{}) }, "空切片不应该panic") } // ---------- end-to-end example ---------- func TestStreamData(t *testing.T) { - // 步骤1: 创建 StreamSQL 实例 - // StreamSQL 是流式 SQL 处理引擎的核心组件,负责管理整个流处理生命周期 + // Step 1: Create a StreamSQL instance + // StreamSQL is the core component of the streaming SQL processing engine, responsible for managing the entire stream processing lifecycle ssql := New() - // 确保测试结束时停止流处理,释放资源 + // Ensure that streaming processing stops at the end of the test and resources are freed defer ssql.Stop() - // 步骤2: 定义流式 SQL 查询语句 - // 这个 SQL 语句展示了 StreamSQL 的核心功能: - // - SELECT: 选择要输出的字段和聚合函数 - // - FROM stream: 指定数据源为流数据 - // - WHERE: 过滤条件,排除 device3 的数据 - // - GROUP BY: 按设备ID分组,配合滚动窗口进行聚合 - // - TumblingWindow('5s'): 5秒滚动窗口,每5秒触发一次计算 - // - avg(), min(): 聚合函数,计算平均值和最小值 - // - window_start(), window_end(): 窗口函数,获取窗口的开始和结束时间 + // Step 2: Define the streaming SQL query statement + // This SQL statement demonstrates the core features of StreamSQL: + // - SELECT: Selects the fields and aggregate functions to output + // - FROM stream: Specifies the data source as stream data + // - WHERE: Filter condition to exclude data from device3 + // - GROUP BY: Grouped by device ID and aggregated with scrolling windows + // - TumblingWindow('5s'): Scrolls the window every 5 seconds, triggering calculation every 5 seconds + // - avg(), min(): Aggregate function, calculates the mean and minimum value + // - window_start(), window_end(): Window function, retrieves the start and end times of the window rsql := "SELECT deviceId,avg(temperature) as avg_temp,min(humidity) as min_humidity ," + "window_start() as start,window_end() as end FROM stream where deviceId!='device3' group by deviceId,TumblingWindow('5s')" - // 步骤3: 执行 SQL 语句,启动流式分析任务 - // Execute 方法会解析 SQL、构建执行计划、初始化窗口管理器和聚合器 + // Step 3: Execute the SQL statement to start the stream analysis task + // The Execute method parses SQL, builds execution plans, initializes window managers and aggregators err := ssql.Execute(rsql) if err != nil { panic(err) } - // 步骤4: 设置测试环境和并发控制 + // Step 4: Set up the test environment and concurrency control var wg sync.WaitGroup wg.Add(1) - // 设置30秒测试超时时间,防止测试无限运行 + // Set a 30-second test timeout to prevent unlimited testing ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) defer cancel() - // 步骤5: 启动数据生产者协程 - // 模拟实时数据流,持续向 StreamSQL 输入数据 + // Step 5: Start the data producer coroutine + // Simulates real-time data flows and continuously inputs data to StreamSQL go func() { defer wg.Done() - // 创建定时器,每秒触发一次数据生成 + // Create a timer that triggers data generation every second ticker := time.NewTicker(1 * time.Second) defer ticker.Stop() for { select { case <-ticker.C: - // 每秒生成10条随机测试数据,模拟高频数据流 - // 这种数据密度可以测试 StreamSQL 的实时处理能力 + // Generates 10 random test data per second, simulating high-frequency data streams + // This data density can test StreamSQL's real-time processing capabilities for i := 0; i < 10; i++ { - // 构造设备数据,包含设备ID、温度和湿度 + // Construct device data, including device ID, temperature, and humidity randomData := map[string]any{ - "deviceId": fmt.Sprintf("device%d", rand.Intn(3)+1), // 随机选择 device1, device2, device3 - "temperature": 20.0 + rand.Float64()*10, // 温度范围: 20-30度 - "humidity": 50.0 + rand.Float64()*20, // 湿度范围: 50-70% + "deviceId": fmt.Sprintf("device%d", rand.Intn(3)+1), // Randomly select device1, device2, device3 + "temperature": 20.0 + rand.Float64()*10, // Temperature range: 20-30 degrees + "humidity": 50.0 + rand.Float64()*20, // Humidity range: 50-70% } - // 将数据添加到流中,触发 StreamSQL 的实时处理 - // Emit 会将数据分发到相应的窗口和聚合器中 + // Add data to the stream to trigger real-time processing in StreamSQL + // Emit distributes data to the corresponding windows and aggregators ssql.Emit(randomData) } case <-ctx.Done(): - // 超时或取消信号,停止数据生成 + // Timeout or cancellation of signals stops data generation return } } }() - // 步骤6: 设置结果处理管道 + // Step 6: Set up the result processing pipeline resultChan := make(chan any, 10) - // 添加计算结果回调函数(Sink) - // 当窗口触发计算时,结果会通过这个回调函数输出 + // Add calculation result callback function (Sink) + // When the window triggers the calculation, the result is output through this callback function ssql.stream.AddSink(func(result []map[string]any) { - // 非阻塞发送,避免阻塞 sink worker + // Non-blocking sending to avoid blocking sink workers select { case resultChan <- result: default: - // Channel 已满,忽略(非阻塞发送) + // Channel is full, ignore (non-blocking sending) } }) - // 步骤7: 启动结果消费者协程 - // 记录收到的结果数量,用于验证测试效果 + // Step 7: Start the result consumer coroutine + // Record the number of results received to verify the test effectiveness var resultCount int64 var countMutex sync.Mutex var consumerWg sync.WaitGroup @@ -956,40 +956,40 @@ func TestStreamData(t *testing.T) { for { select { case <-resultChan: - // 每当收到一个窗口的计算结果时,计数器加1 - // 注释掉的代码可以用于调试,打印每个结果的详细信息 - //fmt.Printf("打印结果: [%s] %v\n", time.Now().Format("15:04:05.000"), result) + // Whenever a window receives a calculation result, the counter increments by 1 + // The commented code can be used for debugging and printing detailed information for each result + //fmt.Printf("Print result: [%s] %v\n", time.Now().Format("15:04:05.000"), result) countMutex.Lock() resultCount++ countMutex.Unlock() case <-ctx.Done(): - // 测试超时,退出消费者 goroutine - // 不关闭 channel,让主程序自动退出时清理 + // Test timeout, exit consumer goroutine + // Do not close the channel, allowing the main program to clean up automatically when exiting return } } }() - // 步骤8: 等待测试完成 - // 等待数据生产者协程结束(30秒超时或手动取消) + // Step 8: Wait for the test to complete + // Wait for the data producer coroutine to finish (30-second timeout or manual cancellation) wg.Wait() - // 停止流处理,确保所有 goroutine 正确退出 + // Stop stream processing and ensure all goroutines exit correctly ssql.Stop() - // 等待一小段时间,确保所有 sink worker 完成当前任务 - // 这样可以确保所有结果都被发送到 channel + // Wait a short while to ensure all sink workers complete the current task + // This ensures that all results are sent to the channel time.Sleep(100 * time.Millisecond) - // 取消 context,通知消费者 goroutine 退出 + // Cancel context to notify consumers to exit the goroutine cancel() - // 等待消费者 goroutine 完成(处理完 channel 中剩余的数据或收到取消信号) + // Wait for the consumer goroutine to complete (after processing the remaining data in the channel or receiving a cancellation signal) consumerWg.Wait() - // 步骤9: 验证测试结果 - // 预期在30秒内应该收到5个窗口的计算结果(每5秒一个窗口) - // 这验证了 StreamSQL 的窗口触发机制是否正常工作 + // Step 9: Verify the test results + // Expect to receive the calculation results of 5 windows within 30 seconds (one window every 5 seconds) + // This verifies whether StreamSQL's window trigger mechanism is working properly countMutex.Lock() finalCount := resultCount countMutex.Unlock() diff --git a/test/e2e/analytic_b_test.go b/test/e2e/analytic_b_test.go index 632291d..d2f84f6 100644 --- a/test/e2e/analytic_b_test.go +++ b/test/e2e/analytic_b_test.go @@ -6,8 +6,8 @@ import ( streamsql "github.com/rulego/streamsql" ) -// BenchmarkMultiCallAnalytic 衡量同一表达式含 1/2/3 个分析调用的每行开销。 -// 走直连路径(EmitSync 同步求值),隔离多调用回代(applyCall×N + wrapper 一次)的成本。 +// BenchmarkMultiCallAnalytic measures the overhead per line for the same expression with 1/2/3 analysis calls. +// Taking a direct connection path (EmitSync synchronous evaluation) isolates the cost of multiple calls (applyCall×N + wrapper in one go). func BenchmarkMultiCallAnalytic(b *testing.B) { cases := []struct{ name, sql string }{ {"1call", `SELECT acc_sum(v) AS r FROM stream`}, @@ -32,9 +32,9 @@ func BenchmarkMultiCallAnalytic(b *testing.B) { } } -// BenchmarkGroupByKeyExpr 衡量 GROUP BY 裸列 vs 表达式分组键的每行开销(窗口路径)。 -// expr 子项触发 injectGroupKeyExprs 逐行对 hour(ts) 求 expr bridge;bare 不触发。 -// 两者差值即表达式分组键注入的开销。CountingWindow(1000) 压低触发频率,凸显逐行成本。 +// BenchmarkGroupByKeyExpr measures the overhead per row (window path) for GROUP BY bare columns vs. expression group keys. +// The expr subterm triggers injectGroupKeyExprs to query the expr bridge line by line for hour(ts); Bare does not trigger. +// The difference between the two is the overhead of injecting the expression block key. CountingWindow(1000) lowers trigger frequency, highlighting the cost of each line. func BenchmarkGroupByKeyExpr(b *testing.B) { cases := []struct { name, sql string @@ -57,7 +57,7 @@ func BenchmarkGroupByKeyExpr(b *testing.B) { if err := ssql.Execute(c.sql); err != nil { b.Fatal(err) } - ssql.AddSink(func(r []map[string]any) {}) // 丢弃窗口输出 + ssql.AddSink(func(r []map[string]any) {}) // Discard window output defer ssql.Stop() b.ResetTimer() b.ReportAllocs() diff --git a/test/e2e/analytic_cdc_test.go b/test/e2e/analytic_cdc_test.go index 89286a6..e515ec8 100644 --- a/test/e2e/analytic_cdc_test.go +++ b/test/e2e/analytic_cdc_test.go @@ -8,8 +8,8 @@ import ( "github.com/stretchr/testify/require" ) -// CDC 场景2:各设备的电流变化后超过阈值(lag 在 WHERE + OVER PARTITION BY)。 -// 预期输出 ts5(d1,current=500) 与 ts8(d2,current=600)。 +// CDC Scenario 2: After the current changes in each device, the threshold is exceeded (lag at WHERE + OVER PARTITION BY). +// Expected outputs ts5(d1, current=500) and ts8(d2, current=600). func TestAnalytic_CDC_LagInWhere_PartitionBy(t *testing.T) { ssql := streamsql.New() err := ssql.Execute("SELECT current, deviceId, ts FROM stream WHERE current > 300 AND lag(current) OVER (PARTITION BY deviceId) < 300") @@ -42,7 +42,7 @@ func TestAnalytic_CDC_LagInWhere_PartitionBy(t *testing.T) { assert.Equal(t, 2, outputs[1]["deviceId"]) } -// lag 在 SELECT(无 OVER):返回前一个值,首行 nil。 +// lag in SELECT (no OVER): Returns the previous value, first line nil. func TestAnalytic_LagInSelect(t *testing.T) { ssql := streamsql.New() require.NoError(t, ssql.Execute("SELECT lag(temperature) AS prev_temp FROM stream")) @@ -61,7 +61,7 @@ func TestAnalytic_LagInSelect(t *testing.T) { assert.Equal(t, 25, r3["prev_temp"]) } -// had_changed 在 WHERE:只输出变化的行(首次视为变化。 +// had_changed In WHERE: only the line with the change is output (initially considered a change). func TestAnalytic_HadChangedInWhere(t *testing.T) { ssql := streamsql.New() require.NoError(t, ssql.Execute("SELECT ts, temperature FROM stream WHERE had_changed(true, temperature) == true")) @@ -87,7 +87,7 @@ func TestAnalytic_HadChangedInWhere(t *testing.T) { assert.Equal(t, 5, outs[2]["ts"]) } -// latest:返回最新非空值,nil 不更新状态。 +// latest: Returns the latest non-null value; nil does not update the status. func TestAnalytic_Latest(t *testing.T) { ssql := streamsql.New() require.NoError(t, ssql.Execute("SELECT latest(temperature) AS lt FROM stream")) @@ -99,12 +99,12 @@ func TestAnalytic_Latest(t *testing.T) { r2, _ := ssql.EmitSync(map[string]any{"temperature": 25}) assert.Equal(t, 25, r2["lt"]) - // nil 不更新状态,仍返回上次非空值 25 + // nil does not update the status and still returns the last non-null value of 25 r3, _ := ssql.EmitSync(map[string]any{"temperature": nil}) assert.Equal(t, 25, r3["lt"]) } -// lag 无 OVER + 普通字段混选。 +// Lag: No OVER + Regular field mixed selection. func TestAnalytic_LagWithPlainField(t *testing.T) { ssql := streamsql.New() require.NoError(t, ssql.Execute("SELECT temperature, lag(temperature) AS prev FROM stream")) @@ -119,7 +119,7 @@ func TestAnalytic_LagWithPlainField(t *testing.T) { assert.Equal(t, 10, r2["prev"]) } -// acc_sum:规则生命周期内累积求和。 +// acc_sum: Accumulate summation over the rule's lifecycle. func TestAnalytic_AccSum(t *testing.T) { ssql := streamsql.New() require.NoError(t, ssql.Execute("SELECT acc_sum(value) AS total FROM stream")) @@ -133,7 +133,7 @@ func TestAnalytic_AccSum(t *testing.T) { assert.Equal(t, 60.0, r3["total"]) } -// acc_avg:累积平均值。 +// acc_avg: Cumulative average. func TestAnalytic_AccAvg(t *testing.T) { ssql := streamsql.New() require.NoError(t, ssql.Execute("SELECT acc_avg(value) AS avg_v FROM stream")) @@ -145,7 +145,7 @@ func TestAnalytic_AccAvg(t *testing.T) { assert.Equal(t, 20.0, r3["avg_v"]) // (10+20+30)/3 } -// acc_max / acc_count:累积极值与计数。 +// acc_max / acc_count: Cumulative positive values and counts. func TestAnalytic_AccMaxCount(t *testing.T) { ssql := streamsql.New() require.NoError(t, ssql.Execute("SELECT acc_max(value) AS mx, acc_count(value) AS cnt FROM stream")) @@ -158,7 +158,7 @@ func TestAnalytic_AccMaxCount(t *testing.T) { assert.Equal(t, int64(3), r3["cnt"]) } -// changed_col:变化时返回新值,未变化返回 nil。 +// changed_col: Returns a new value when it changes; returns nil when it does not change. func TestAnalytic_ChangedCol(t *testing.T) { ssql := streamsql.New() require.NoError(t, ssql.Execute("SELECT changed_col(true, temperature) AS chg FROM stream")) @@ -174,24 +174,24 @@ func TestAnalytic_ChangedCol(t *testing.T) { assert.Equal(t, 25, r3["chg"]) } -// lag OVER WHEN:满足条件才更新状态,否则复用上次结果。 +// lag OVER WHEN: Updates status only when conditions are met; otherwise, the previous result is reused. func TestAnalytic_LagWithWhen(t *testing.T) { ssql := streamsql.New() require.NoError(t, ssql.Execute("SELECT lag(value) OVER (WHEN value > 20) AS prev FROM stream")) defer ssql.Stop() - // value=10 不满足 WHEN → lag 状态不更新,prev=nil + // value=10 does not meet the WHEN → lag status not updated, prev=nil r1, _ := ssql.EmitSync(map[string]any{"value": 10}) assert.Nil(t, r1["prev"]) - // value=25 满足 → 状态更新,但无前一个有效值,prev=nil + // value=25 meets → status update, but no previous valid value, prev=nil r2, _ := ssql.EmitSync(map[string]any{"value": 25}) assert.Nil(t, r2["prev"]) - // value=30 满足 → prev=上一次有效值 25 + // value=30 satisfies → prev=last RMS value 25 r3, _ := ssql.EmitSync(map[string]any{"value": 30}) assert.Equal(t, 25, r3["prev"]) } -// lag 多偏移:lag(value, 2) 返回前 2 个值。 +// lag Multiple offsets: lag(value, 2) returns the first 2 values. func TestAnalytic_LagOffset(t *testing.T) { ssql := streamsql.New() require.NoError(t, ssql.Execute("SELECT lag(value, 2) AS prev2 FROM stream")) @@ -206,8 +206,8 @@ func TestAnalytic_LagOffset(t *testing.T) { assert.Equal(t, 20, r4["prev2"]) } -// CDC 场景1:整流变化后超过阈值(无 PARTITION 的 lag 在 WHERE)。 -// 预期输出 ts2(400)、ts4(500)。 +// CDC Scenario 1: After rectification changes, the threshold is exceeded (lag without PARTITION is WHERE in WHERE). +// Expected output ts2 (400), ts4 (500). func TestAnalytic_CDC_LagInWhere_NoPartition(t *testing.T) { ssql := streamsql.New() require.NoError(t, ssql.Execute("SELECT concurrency, ts FROM stream WHERE concurrency > 300 AND lag(concurrency) <= 300")) @@ -232,7 +232,7 @@ func TestAnalytic_CDC_LagInWhere_NoPartition(t *testing.T) { assert.Equal(t, 500, outs[1]["concurrency"]) } -// A1:OVER WHEN 含嵌套函数调用(CDC S2 状态时长的解析,之前 parseOverWhen 截断)。 +// A1: OVER WHEN includes nested function calls (CDC S2 state duration resolution, previously parseOverWhen truncated). func TestAnalytic_LagOverWhenNestedFunc(t *testing.T) { ssql := streamsql.New() err := ssql.Execute("SELECT lag(status) OVER (WHEN had_changed(true, status)) AS prev_status FROM stream") @@ -241,7 +241,7 @@ func TestAnalytic_LagOverWhenNestedFunc(t *testing.T) { ssql.EmitSync(map[string]any{"status": 1}) } -// A2:had_changed(ignoreNull=true) 遇 nil 不覆盖基准。 +// A2: had_changed(ignoreNull=true) If nil does not cover the benchmark. func TestAnalytic_HadChangedIgnoreNull(t *testing.T) { ssql := streamsql.New() require.NoError(t, ssql.Execute("SELECT had_changed(true, temperature) AS chg FROM stream")) @@ -250,11 +250,11 @@ func TestAnalytic_HadChangedIgnoreNull(t *testing.T) { r1, _ := ssql.EmitSync(map[string]any{"temperature": 23}) assert.Equal(t, true, r1["chg"], "首次视为变化") - // nil 不触发变化、不更新基准(基准仍为 23) + // NIL does not trigger changes or update the baseline (the baseline remains at 23) r2, _ := ssql.EmitSync(map[string]any{"temperature": nil}) assert.Equal(t, false, r2["chg"]) - // 与基准 23 相等 → 未变化(A2 修复前因基准被 nil 污染而误报 true) + // Equal to baseline 23 → unchanged (A2 false report due to NIL contamination before remediation of A2) r3, _ := ssql.EmitSync(map[string]any{"temperature": 23}) assert.Equal(t, false, r3["chg"], "nil 后基准保留 23,23==23 未变化") @@ -262,7 +262,7 @@ func TestAnalytic_HadChangedIgnoreNull(t *testing.T) { assert.Equal(t, true, r4["chg"]) } -// A3:lag 第 4 参数 ignoreNull,nil 值跳过不存入历史。 +// A3: lag 4th parameter ignoreNull, nil value skips and is not stored in history. func TestAnalytic_LagIgnoreNull(t *testing.T) { ssql := streamsql.New() require.NoError(t, ssql.Execute("SELECT lag(value, 1, -1, true) AS lg FROM stream")) @@ -271,15 +271,15 @@ func TestAnalytic_LagIgnoreNull(t *testing.T) { r1, _ := ssql.EmitSync(map[string]any{"value": 10}) assert.Equal(t, -1.0, r1["lg"], "首次无前值返回 default=-1") - // nil 被 ignoreNull 跳过,不进历史 + // nil was skipped by ignoreNull and doesn't go into history ssql.EmitSync(map[string]any{"value": nil}) - // 上一个有效值是 10 + // The previous valid value was 10 r3, _ := ssql.EmitSync(map[string]any{"value": 30}) assert.Equal(t, 10, r3["lg"]) } -// A4:acc_count 计数非数字列。 +// A4: acc_count Count non-numeric columns. func TestAnalytic_AccCountNonNumeric(t *testing.T) { ssql := streamsql.New() require.NoError(t, ssql.Execute("SELECT acc_count(name) AS cnt FROM stream")) @@ -291,7 +291,7 @@ func TestAnalytic_AccCountNonNumeric(t *testing.T) { assert.Equal(t, int64(3), r3["cnt"], "acc_count 应计数非数字表达式结果") } -// B2:WHEN 满足→不满足→满足,不满足时复用上次结果。 +// B2: WHEN the condition is met→ if not, → is satisfied; if not, the previous result is reused. func TestAnalytic_WhenTransition(t *testing.T) { ssql := streamsql.New() require.NoError(t, ssql.Execute("SELECT lag(value) OVER (WHEN value > 20) AS prev FROM stream")) diff --git a/test/e2e/analytic_combo_test.go b/test/e2e/analytic_combo_test.go index d408ca2..75094e6 100644 --- a/test/e2e/analytic_combo_test.go +++ b/test/e2e/analytic_combo_test.go @@ -10,11 +10,11 @@ import ( "github.com/stretchr/testify/require" ) -// 本文件专门覆盖"多特性真实组合"场景:JOIN×分析、聚合间算术、双分析算术、 -// LEFT JOIN×COALESCE 空值填充、多分组多指标不变式。期望值均按 SQL 语义推导, -// 用于验证组合路径的正确性;不与单特性文件重复。 +// This document specifically covers scenarios for "multi-feature true combinations": JOIN× analysis, inter-aggregate arithmetic, dual-analysis arithmetic, +// LEFT JOIN× COALESCE NULL filling, multi-group, multi-indicator invariants. Expected values are derived according to SQL semantics, +// Used to verify the correctness of the combined path; Does not duplicate single-attribute files. -// runDirectJoin 在直连路径上跑 JOIN 查询:Execute 后注册表,逐条 EmitSync 收非 nil 行。 +// runDirectJoin runs JOIN queries on the direct connection path: after executing, the registry receives each EmitSync line by line that is not nil. func runDirectJoin(t *testing.T, sql, table string, tableRows []map[string]any, inputs []map[string]any) []map[string]any { t.Helper() ssql := streamsql.New() @@ -38,13 +38,13 @@ func runDirectJoin(t *testing.T, sql, table string, tableRows []map[string]any, return out } -// === JOIN × 分析函数(元数据增强后按增强字段分区做 lag/累计)=== +// === JOIN × Analysis Function (After metadata enhancement, lag/cumulative is done by partitioning by enhanced fields) === -// JOIN 增强出 location 后,lag(temp) 按 location 分区取上一条 temp。 -// meta: d1→plantA, d2→plantB, d3→plantA。 +// After JOINing to enhance the location, lag(temp) partitions the previous temp by location. +// meta: d1→plantA, d2→plantB, d3→plantA. // -// PARTITION BY 连接字段 m.location 经 resolvePartitionField 走 fieldpath 解析 -// (增强行里 location 嵌套在 m 下,裸 row[k] 恒 nil 会退化成全局分区)。 +// PARTITION BY connects fields m.location via resolvePartitionField and parses the fieldpath +// (If the enhanced row location is nested under m, the bare row[k] constant nil will degenerate into a global partition.) func TestScenario_JoinAnalytic_LagByLocation(t *testing.T) { const sql = `SELECT deviceId, m.location AS loc, lag(temp) OVER (PARTITION BY m.location) AS prev FROM stream JOIN meta m ON deviceId = m.deviceId` @@ -54,11 +54,11 @@ FROM stream JOIN meta m ON deviceId = m.deviceId` {"deviceId": "d3", "location": "plantA"}, } in := []map[string]any{ - {"deviceId": "d1", "temp": 10}, // plantA 首条 → prev=nil - {"deviceId": "d2", "temp": 20}, // plantB 首条 → prev=nil - {"deviceId": "d3", "temp": 30}, // plantA 第2条 → prev=10 - {"deviceId": "d1", "temp": 40}, // plantA 第3条 → prev=30 - {"deviceId": "d2", "temp": 50}, // plantB 第2条 → prev=20 + {"deviceId": "d1", "temp": 10}, // plantA first → prev=nil + {"deviceId": "d2", "temp": 20}, // plantB First → prev=nil + {"deviceId": "d3", "temp": 30}, // plantA Article 2 → prev=10 + {"deviceId": "d1", "temp": 40}, // plantA Article 3 → prev=30 + {"deviceId": "d2", "temp": 50}, // plantB Article 2 → prev=20 } got := runDirectJoin(t, sql, "meta", meta, in) wantPrev := []any{nil, nil, 10.0, 30.0, 20.0} @@ -80,7 +80,7 @@ FROM stream JOIN meta m ON deviceId = m.deviceId` } } -// JOIN 增强后 acc_sum(temp) 按 location 分区跨行累计(同 LagByLocation,分区键走 fieldpath)。 +// After JOIN enhancement, acc_sum (temp) is used to accumulate across rows by partitioning location (same as LagByLocation, partition keys follow the fieldpath). func TestScenario_JoinAnalytic_AccSumByLocation(t *testing.T) { const sql = `SELECT m.location AS loc, acc_sum(temp) OVER (PARTITION BY m.location) AS s FROM stream JOIN meta m ON deviceId = m.deviceId` @@ -108,9 +108,9 @@ FROM stream JOIN meta m ON deviceId = m.deviceId` } } -// === 聚合间算术(窗口聚合结果再做运算)=== +// === Arithmetic between aggregations (perform operations after aggregating window results) === -// 窗口 max-min 极差。CountingWindow(2) 三窗:[3,7]→4, [1,4]→3, [10,2]→8。 +// Window max-min is extremely poor. CountingWindow(2) Three-window: [3,7]→4, [1,4]→3, [10,2]→8. func TestScenario_AggArithmetic_WindowRange(t *testing.T) { in := []map[string]any{{"v": 3}, {"v": 7}, {"v": 1}, {"v": 4}, {"v": 10}, {"v": 2}} got := runWindow(t, `SELECT max(v) - min(v) AS rng FROM stream GROUP BY CountingWindow(2)`, in) @@ -119,8 +119,8 @@ func TestScenario_AggArithmetic_WindowRange(t *testing.T) { } } -// 手算均值 sum/count 应与内置 avg 一致(浮点输入避免整除)。 -// 三窗 [3,7]→5, [1,4]→2.5, [10,2]→6。 +// The manual mean sum/count should match the built-in avg (floating-point input avoids division). +// Three-window [3,7]→5, [1,4]→2.5, [10,2]→6. func TestScenario_AggArithmetic_ManualAvgMatchesBuiltIn(t *testing.T) { in := []map[string]any{{"v": 3.0}, {"v": 7.0}, {"v": 1.0}, {"v": 4.0}, {"v": 10.0}, {"v": 2.0}} got := runWindow(t, `SELECT sum(v) / count(*) AS manual, avg(v) AS built FROM stream GROUP BY CountingWindow(2)`, in) @@ -135,12 +135,12 @@ func TestScenario_AggArithmetic_ManualAvgMatchesBuiltIn(t *testing.T) { } } -// === 双分析函数算术(同一表达式含两个分析调用)=== +// === Dual analysis function arithmetic (the same expression contains two analysis calls) === -// 运行期极差 acc_max-acc_min。v:3,1,4,1,5 → acc_max[3,3,4,4,5] acc_min[3,1,1,1,1] → 极差[0,2,3,3,4]。 +// Operating period range: acc_max-acc_min. v:3,1,4,1,5 → acc_max[3,3,4,4,5] acc_min[3,1,1,1,1] → extreme [0,2,3,3,4]. // -// 同表达式含两个分析调用:splitAnalyticExprMulti 抽全部调用各替换占位 -// (__analytic_self__ / __analytic_self_1__),求值期各算各的、注入占位再求 wrapper。 +// The same expression contains two analysis calls: splitAnalyticExprMulti draws all calls and replaces placeholders +// (__analytic_self__ / __analytic_self_1__), calculate each valuation period separately, inject placeholder, then find the wrapper. func TestScenario_TwoAnalyticArithmetic_RunningRange(t *testing.T) { in := []map[string]any{{"v": 3}, {"v": 1}, {"v": 4}, {"v": 1}, {"v": 5}} got := runDirect(t, `SELECT acc_max(v) - acc_min(v) AS rr FROM stream`, in) @@ -155,9 +155,9 @@ func TestScenario_TwoAnalyticArithmetic_RunningRange(t *testing.T) { } } -// 三个分析调用 + 混合算术:acc_max + acc_min + acc_sum。 -// v:3,1,4,1,5 → max[3,3,4,4,5] min[3,1,1,1,1] sum[3,4,8,9,14] → 三者之和[9,8,13,14,20]。 -// 验证同表达式不限于 2 个调用,占位 __analytic_self__/__analytic_self_1__/__analytic_self_2__ 各自回代。 +// Three analysis calls + mixed arithmetic: acc_max + acc_min + acc_sum. +// v:3,1,4,1,5 → max[3,3,4,4,5] min[3,1,1,1,1] sum[3,4,8,9,14] → The sum of the three [9,8,13,14,20]. +// Verification of the same expression is not limited to two calls; placeholders __analytic_self__/__analytic_self_1__/__analytic_self_2__ are each substituted. func TestScenario_ThreeAnalyticArithmetic_SumOfRunning(t *testing.T) { in := []map[string]any{{"v": 3}, {"v": 1}, {"v": 4}, {"v": 1}, {"v": 5}} got := runDirect(t, `SELECT acc_max(v) + acc_min(v) + acc_sum(v) AS s FROM stream`, in) @@ -172,8 +172,8 @@ func TestScenario_ThreeAnalyticArithmetic_SumOfRunning(t *testing.T) { } } -// 两个分析调用 + 乘法与标量:acc_max * acc_min。 -// v:3,1,4,1,5 → [9,3,4,4,5]。验证算术运算符不限加减,expr bridge 支持的运算符皆可。 +// Two analysis calls + multiplication and scalar: acc_max * acc_min. +// v:3,1,4,1,5 → [9,3,4,4,5]. Verification arithmetic operators are not limited by addition or subtraction; all operators supported by expr bridge are acceptable. func TestScenario_TwoAnalyticArithmetic_Multiply(t *testing.T) { in := []map[string]any{{"v": 3}, {"v": 1}, {"v": 4}, {"v": 1}, {"v": 5}} got := runDirect(t, `SELECT acc_max(v) * acc_min(v) AS p FROM stream`, in) @@ -188,9 +188,9 @@ func TestScenario_TwoAnalyticArithmetic_Multiply(t *testing.T) { } } -// 复杂优先级表达式:acc_max/10 - acc_min*10 + acc_sum(三个调用 + 乘除加减混合)。 +// Complex priority expression: acc_max/10 - acc_min*10 + acc_sum (three calls + multiplication, division, addition, subtraction, mix). // v:3,1,4,1,5 → max[3,3,4,4,5] min[3,1,1,1,1] sum[3,4,8,9,14] -// row= max/10 - min*10 + sum:-26.7,-5.7,-1.6,-0.6,4.5。验证 */优先于+-、多调用回代正确。 +// row= max/10 - min*10 + sum:-26.7,-5.7,-1.6,-0.6,4.5. Verify that */ takes precedence over +-, and multiple calls are correct. func TestScenario_ThreeAnalyticArithmetic_ComplexPrecedence(t *testing.T) { in := []map[string]any{{"v": 3}, {"v": 1}, {"v": 4}, {"v": 1}, {"v": 5}} got := runDirect(t, `SELECT acc_max(v)/10 - acc_min(v)*10 + acc_sum(v) AS c FROM stream`, in) @@ -205,9 +205,9 @@ func TestScenario_ThreeAnalyticArithmetic_ComplexPrecedence(t *testing.T) { } } -// === LEFT JOIN × COALESCE(无匹配行空值填充)=== +// === LEFT JOIN × COALESCE (blank pad without matching row) === -// LEFT JOIN 无匹配时 m.location 为 nil,coalesce 填默认值。 +// If LEFT JOIN has no match, m.location is nil, coalesce is set to the default value. func TestScenario_LeftJoinCoalesce_FillUnknown(t *testing.T) { const sql = `SELECT deviceId, coalesce(m.location, 'unknown') AS loc FROM stream LEFT JOIN meta m ON deviceId = m.deviceId` @@ -216,8 +216,8 @@ FROM stream LEFT JOIN meta m ON deviceId = m.deviceId` {"deviceId": "d2", "location": "plantB"}, } in := []map[string]any{ - {"deviceId": "d1"}, // 匹配 → plantA - {"deviceId": "d9"}, // 无匹配 → unknown + {"deviceId": "d1"}, // Match → plantA + {"deviceId": "d9"}, // No match → unknown } got := runDirectJoin(t, sql, "meta", meta, in) want := []map[string]any{ @@ -229,10 +229,10 @@ FROM stream LEFT JOIN meta m ON deviceId = m.deviceId` } } -// === 多分组多指标不变式(校验指标间内在关系,非逐值)=== +// === Multi-group, multi-indicator invariant formula (check the intrinsic relationships between indicators, not by value) === -// 每窗按 device 分组,断言每组满足:min<=avg<=max 且 sum==count*avg。 -// CountingWindow(2) 为全局计数窗口:6 事件 → 3 窗,窗内再按 device 分组。 +// Each window is grouped by device, asserting that each group satisfies: min<=avg<=max, and sum==count*avg. +// CountingWindow(2) is the global counting window: 6 events → 3 windows, which are grouped by device within the window. func TestScenario_MultiGroupMetrics_Invariants(t *testing.T) { in := []map[string]any{ {"device": "A", "v": 1.0}, {"device": "A", "v": 3.0}, @@ -267,10 +267,10 @@ func abs(f float64) float64 { return f } -// === 补充:窗口聚合结果用于 HAVING 之外的复合投影 === +// === Supplement: Window aggregation results are used for composite projections outside HAVING === -// 同一窗口同时算 sum 与 avg,并对结果做算术(avg*count 应==sum)。 -// 校验聚合间一致性:与 ManualAvg 互补,这里断言 sum - avg*count ≈ 0。 +// In the same window, compute both sum and avg, and perform arithmetic on the result (avg*count should ==sum). +// Verify inter-aggregation consistency: complementary to ManualAvg, here asserting sum - avg*count ≈ 0. func TestScenario_WindowAggConsistency(t *testing.T) { in := []map[string]any{{"v": 2.0}, {"v": 4.0}, {"v": 6.0}, {"v": 8.0}} got := runWindow(t, @@ -278,7 +278,7 @@ func TestScenario_WindowAggConsistency(t *testing.T) { s := sortedFloatField(got, "s") a := sortedFloatField(got, "a") c := sortedFloatField(got, "c") - // 两窗 [2,4]→sum6/avg3/count2;[6,8]→sum14/avg7/count2 + // Two windows [2,4]→sum6/avg3/count2; [6,8]→sum14/avg7/count2 wantSum := []float64{6, 14} wantAvg := []float64{3, 7} wantCnt := []float64{2, 2} @@ -292,14 +292,14 @@ func TestScenario_WindowAggConsistency(t *testing.T) { } } -// === 第二批:HAVING 引用未选出聚合 / GROUP BY 表达式 / 多 OVER 分区 / 分析嵌 CASE 或 coalesce / WHERE与分析顺序 === +// === Second batch: HAVING citation of unselected aggregation / GROUP BY expressions / multi-OVER partitions / analysis CASE or coalesce / WHERE and analysis order === -// HAVING 引用未在 SELECT 出现的聚合 max(v)。CountingWindow(3) 为按 key 计数窗: -// 每 device 各计满 3 条触发。A=[1,2,1]→c3 max2(max>4 ✗ 过滤);B=[5,6,1]→c3 max6(>4 ✓)。 -// 期望仅 B 一行 c3——证明未选出的 max(v) 被正确补算(否则 nil>4 恒假→空)。 +// HAVING references aggregation max(v) that does not appear in SELECT. CountingWindow(3) is the key counting window: +// Each device has 3 triggers. A = [1,2,1]→c3 max2(max>4 ✗ filter); B=[5,6,1]→c3 max6 (>4 ✓). +// Expect only line B c3 — proving that the unselected max(v) is correctly supplemented (otherwise, nil>4 is always false → empty). // -// extractHavingAggregates 把 max(v) 注册为隐藏聚合 __having_0__ 让 aggregator 补算, -// HAVING 文本改写为 __having_0__;输出剥离该键。 +// extractHavingAggregates registers max(v) as a hidden aggregation __having_0__ lets aggregator complete the computation, +// HAVING text rewritten as __having_0__; Output the stripped key. func TestScenario_Having_NonSelectedAggregate(t *testing.T) { in := []map[string]any{ {"device": "A", "v": 1}, {"device": "B", "v": 5}, {"device": "A", "v": 2}, @@ -313,17 +313,17 @@ func TestScenario_Having_NonSelectedAggregate(t *testing.T) { byDev[d] = toFloatVal(r["c"]) } if len(byDev) != 1 || byDev["B"] != 3 { - t.Errorf("HAVING max(v)>4 (max 未选出): got %v, want {B:3}", byDev) + t.Errorf("HAVING max(v)>4 (max not selected): got %v, want {B:3}", byDev) } } -// GROUP BY 函数表达式 upper(device)。CountingWindow(4) 单窗,upper 后分两组。 -// GROUP BY 函数表达式 upper(device)。CountingWindow(2) 为按 key 计数窗:input aa,AA,bb,BB -// → upper 各为 AA,AA,BB,BB。AA 满 2 触发 c2、BB 满 2 触发 c2。期望 {AA:2, BB:2}。 -// 若 upper() 未生效(按原始 device 分组),则 4 个不同值各 1 条、无 key 满 2 → 空。 +// GROUP BY function expression upper(device). CountingWindow(4) Single window, after the upper window, split into two groups. +// GROUP BY function expression upper(device). CountingWindow(2) is the key-based counting window: input aa, AA, bb, BB +// → upper are AA, AA, BB, BB. AA triggers c2 when 2 hits, BB triggers c2 when 2 hits. Expectation {AA:2, BB:2}. +// If upper() is not active (grouped by original device), then 4 different values each have 1 line, and no key fills 2 → empty. // -// parser 把 upper(device) 读成整体分组键;injectGroupKeyExprs 在 Window.Add 前求值注入; -// projectGroupColumns 把分组键值重命名到输出别名 d。 +// parser reads upper(device) as the overall group key; injectGroupKeyExprs is evaluated and injected before Window.Add; +// projectGroupColumns renames the group key to output the alias d. func TestScenario_GroupBy_FunctionExpression(t *testing.T) { in := []map[string]any{ {"device": "aa"}, {"device": "AA"}, {"device": "bb"}, {"device": "BB"}, @@ -340,9 +340,9 @@ func TestScenario_GroupBy_FunctionExpression(t *testing.T) { } } -// GROUP BY 时间函数表达式 hour(timestamp)。hour() 取 "YYYY-MM-DD HH:MM:SS" 串的小时。 -// CountingWindow(2) 按 key(小时值)计数:hour10 有 2 条→触发 c2,hour11 有 2 条→触发 c2。 -// 期望 {10:2, 11:2}。分组键保留原始类型,hour() 返回 int,故 h 列为数值 10/11。 +// GROUP BY time function expression hour(timestamp). hour() takes the hour of the "YYYY-MM-DD HH:MM:SS" string. +// CountingWindow(2) Counts by key (hour): hour10 triggers 2 → c2, hour11 triggers 2 → c2. +// Expectation {10:2, 11:2}. The group key retains the original type, and hour() returns int, so the h column is a numeric value 10/11. func TestScenario_GroupBy_HourExpression(t *testing.T) { in := []map[string]any{ {"timestamp": "2026-07-12 10:00:00"}, @@ -361,8 +361,8 @@ func TestScenario_GroupBy_HourExpression(t *testing.T) { } } -// 同一 SELECT 两个分析函数、不同 PARTITION BY,状态机应独立无串扰。 -// p1=lag(a) PARTITION BY k1;p2=lag(b) PARTITION BY k2。 +// For the same SELECT with two analysis functions and different PARTITION BY, the state machine should be independent and free of crosstalk. +// p1=lag(a) PARTITION BY k1; p2=lag(b) PARTITION BY k2. func TestScenario_MultiOver_DifferentPartitions(t *testing.T) { in := []map[string]any{ {"k1": 1, "a": 10, "k2": "x", "b": 100}, @@ -371,15 +371,15 @@ func TestScenario_MultiOver_DifferentPartitions(t *testing.T) { {"k1": 2, "a": 40, "k2": "x", "b": 400}, } got := runDirect(t, `SELECT lag(a) OVER (PARTITION BY k1) AS p1, lag(b) OVER (PARTITION BY k2) AS p2 FROM stream`, in) - // p1 按 k1:k1=1[行0,2]→nil,10;k1=2[行1,3]→nil,20 - // p2 按 k2:x[行0,1,3]→nil,100,200;y[行2]→nil + // p1 According to k1: k1=1 [row 0,2] →nil,10; k1=2 [row 1,3] →nil,20 + // p2 According to k2: x[row 0,1,3]→nil,100,200; y[row 2]→nil assertNumericField(t, "multi-over p1", got, "p1", []any{nil, nil, 10.0, 20.0}) assertNumericField(t, "multi-over p2", got, "p2", []any{nil, 100.0, nil, 200.0}) } -// 分析函数嵌在 CASE 里:lag(temp) 解析后回代进 CASE 求值。 -// temp 10,25,15,30 → lag[nil,10,25,15] → lag>20 ? up:down → [down,down,up,down](nil 比较判假→down)。 -// 标量套分析:applyWrapper 经 expr bridge→expr 包两级求值,CASE 走 expr 包求值器。 +// The analysis function is embedded in the CASE: lag(temp) parses and then substitutes back into the CASE for evaluation. +// temp 10,25,15,30 → lag[nil,10,25,15] → lag>20? up:down → [down, down, up, down] (nil is a false → down). +// Scalar Suite Analysis: applyWrapper evaluates via expr bridge → expr package, and CASE evaluates via expr package. func TestScenario_AnalyticInside_Case(t *testing.T) { in := []map[string]any{{"temp": 10}, {"temp": 25}, {"temp": 15}, {"temp": 30}} got := runDirect(t, `SELECT CASE WHEN lag(temp) > 20 THEN 'up' ELSE 'down' END AS trend FROM stream`, in) @@ -394,8 +394,8 @@ func TestScenario_AnalyticInside_Case(t *testing.T) { } } -// 对照组:JOIN + 分析函数,但 PARTITION BY 的是**流自有列** deviceId(行内存在)。 -// 与对照(分区键为连接字段)对比:这里应正确按 deviceId 分区。 +// Control group: JOIN + parsing function, but PARTITION BY is **stream with columns** deviceId (in-line existence). +// Comparison with the Comparison (partition key is the Connect field): Here, the deviceId partition should be correctly pressed. func TestScenario_JoinAnalytic_PartitionByStreamColumn(t *testing.T) { const sql = `SELECT deviceId, lag(temp) OVER (PARTITION BY deviceId) AS prev FROM stream JOIN meta m ON deviceId = m.deviceId` @@ -413,27 +413,27 @@ FROM stream JOIN meta m ON deviceId = m.deviceId` assertNumericField(t, "JOIN+analytic 分区流列", got, "prev", []any{nil, nil, 10.0, 20.0}) } -// coalesce 包分析函数:首行 lag=nil → 填默认 -1。 -// temp 10,20,30 → lag[nil,10,20] → coalesce(lag,-1)=[-1,10,20]。 -// 标量套分析:applyWrapper 不在 nil 上短路,coalesce(nil,-1)→-1。 +// coalesce package analysis function: first line lag = nil → set to default -1. +// temp 10,20,30 → lag[nil,10,20] → coalesce(lag,-1)=[-1,10,20]. +// Scalar sleeve analysis: applyWrapper does not short circuit on nil, coalesce(nil, -1) →-1. func TestScenario_CoalesceWraps_Analytic(t *testing.T) { in := []map[string]any{{"temp": 10}, {"temp": 20}, {"temp": 30}} got := runDirect(t, `SELECT coalesce(lag(temp), -1) AS safe_prev FROM stream`, in) assertNumericField(t, "coalesce(lag,-1)", got, "safe_prev", []any{-1.0, 10.0, 20.0}) } -// WHERE 与分析函数的求值顺序(标准 SQL:WHERE 先过滤,分析只看过滤后的行)。 -// temp 10,20,15,30,WHERE temp>12 保留 20,15,30;lag 在过滤后流上 → [nil,20,15]; -// d=temp-lag → [nil,-5,15]。普通 WHERE 场景;CDC 模式(WHERE 引用分析)仍分析在先。 +// The evaluation order of WHERE and the analysis function (standard SQL: WHERE filters first, analysis only looks at the rows after filtering). +// temp 10,20,15,30,WHERE temp>12 Retain 20,15,30; lag on filtered stream → [nil,20,15]; +// d=temp-lag → [nil,-5,15]. Regular WHERE scenes; The CDC model (WHERE citation analysis) still leads the analysis. func TestScenario_WhereVsAnalytic_Ordering(t *testing.T) { in := []map[string]any{{"temp": 10}, {"temp": 20}, {"temp": 15}, {"temp": 30}} got := runDirect(t, `SELECT temp, temp - lag(temp) AS d FROM stream WHERE temp > 12`, in) assertNumericField(t, "WHERE先于分析", got, "d", []any{nil, -5.0, 15.0}) } -// 分析函数组合与边界测试:多函数共存、与分区/窗口/条件组合,找潜在 bug。 +// Analyze function combinations and boundary testing: coexist with multiple functions, combine with partitions/windows/conditions to identify potential bugs. -// 多个分析函数在同一查询共存,各自独立状态。 +// Multiple analysis functions coexist within the same query, each in its own independent state. func TestCombo_MultipleAnalytics_Direct(t *testing.T) { ssql := streamsql.New() require.NoError(t, ssql.Execute( @@ -446,20 +446,20 @@ func TestCombo_MultipleAnalytics_Direct(t *testing.T) { require.NoError(t, err) got = append(got, out) } - // lag: nil,1,2 ; acc_sum(b): 10,30,60 ; changed_col(c): x(首),nil(未变),y(变→cc 省略 nil) + // lag: nil,1,2; acc_sum(b): 10,30,60; changed_col(c): x (beginning), nil (unchanged), y (changed→cc omitted nil) assert.Nil(t, got[0]["p"]) assert.InDelta(t, 10.0, toFloatVal(got[0]["s"]), 0.01) assert.Equal(t, "x", got[0]["cc"]) assert.InDelta(t, 1.0, toFloatVal(got[1]["p"]), 0.01) assert.InDelta(t, 30.0, toFloatVal(got[1]["s"]), 0.01) - _, hasCC1 := got[1]["cc"] // c 未变 → cc 省略 + _, hasCC1 := got[1]["cc"] // c unchanged → cc omitted assert.False(t, hasCC1) assert.InDelta(t, 2.0, toFloatVal(got[2]["p"]), 0.01) assert.InDelta(t, 60.0, toFloatVal(got[2]["s"]), 0.01) assert.Equal(t, "y", got[2]["cc"]) } -// acc_sum + PARTITION BY:每分区各自累加。 +// acc_sum + PARTITION BY: Each partition is accumulated separately. func TestCombo_AccSum_Partition(t *testing.T) { ssql := streamsql.New() require.NoError(t, ssql.Execute( @@ -472,7 +472,7 @@ func TestCombo_AccSum_Partition(t *testing.T) { require.NoError(t, err) got = append(got, out) } - // 输入序: A1, B2, A3, B4 → A 累加 1,4;B 累加 2,6(分区独立,互不混入) + // Input order: A1, B2, A3, B4 → A cumulative 1,4; B cumulative 2,6 (partition-independent, no mixing) assert.Equal(t, "A", got[0]["k"]) assert.InDelta(t, 1.0, toFloatVal(got[0]["s"]), 0.01) assert.Equal(t, "B", got[1]["k"]) @@ -483,13 +483,13 @@ func TestCombo_AccSum_Partition(t *testing.T) { assert.InDelta(t, 6.0, toFloatVal(got[3]["s"]), 0.01) } -// 条件 ACC + PARTITION:每分区各自的 start/reset 生命周期。 +// Condition ACC + PARTITION: The start/reset lifecycle for each partition. func TestCombo_CondAcc_Partition(t *testing.T) { ssql := streamsql.New() require.NoError(t, ssql.Execute( `SELECT k, acc_count(v, v > 1, v < 0) OVER (PARTITION BY k) AS c FROM stream`)) defer ssql.Stop() - // A: 1,2,3,-1,1 → 0,1,2,0,0 ; B: 5,5 → 5>1 计 1,2 + // A: 1,2,3,-1,1 → 0,1,2,0,0; B: 5.5 → 5>1 Total: 1.2 in := []map[string]any{{"k": "A", "v": 1}, {"k": "A", "v": 2}, {"k": "B", "v": 5}, {"k": "A", "v": 3}, {"k": "B", "v": 5}, {"k": "A", "v": -1}} var got []map[string]any for _, r := range in { @@ -502,16 +502,16 @@ func TestCombo_CondAcc_Partition(t *testing.T) { k, _ := r["k"].(string) byK[k] = append(byK[k], toInt64Val(r["c"])) } - // A 依次: 0,1,2,0 (A 的 -1 归零);B: 1,2 + // A in order: 0, 1, 2, 0 (A zeros -1); B: 1,2 assert.Equal(t, []int64{0, 1, 2, 0}, byK["A"], "A 分区条件累计") assert.Equal(t, []int64{1, 2}, byK["B"], "B 分区条件累计") } -// lag 进窗口:对窗口聚合输出跨窗口 lag。 +// lag into window: outputs a lag across windows when aggregated. func TestCombo_Lag_InWindow(t *testing.T) { d := []map[string]any{{"t": 10.0}, {"t": 20.0}, {"t": 30.0}, {"t": 40.0}} got := runWindow(t, `SELECT lag(avg(t)) AS p FROM stream GROUP BY CountingWindow(1)`, d) - // 每事件一窗,avg=t:10,20,30,40;lag 跨窗口: nil,10,20,30 + // One window per event, avg = t: 10, 20, 30, 40; lag across windows: nil, 10, 20, 30 var vals []float64 nilCnt := 0 for _, r := range got { @@ -526,29 +526,29 @@ func TestCombo_Lag_InWindow(t *testing.T) { assert.Equal(t, []float64{10, 20, 30}, vals, "lag(avg) 跨窗口非 nil 值: 10,20,30") } -// had_changed 进窗口:检测窗口聚合输出是否变化。 +// had_changed In-window: Detects whether the window aggregate output has changed. func TestCombo_HadChanged_InWindow(t *testing.T) { d := []map[string]any{{"t": 10.0}, {"t": 10.0}, {"t": 20.0}, {"t": 20.0}} got := runWindow(t, `SELECT had_changed(true, avg(t)) AS h FROM stream GROUP BY CountingWindow(2)`, d) - // 窗口均值: 10,20 → had_changed: true(首),true(变) + // Window Average: 10, 20 → had_changed: true (initial), true (variable) bools := []bool{} for _, r := range got { b, _ := r["h"].(bool) bools = append(bools, b) } - // 两个窗口都 true(首+变);若串扰/错位会出 false + // Both windows are true (first + change); If crosstalk/misalignment occurs, false will appear require.Len(t, bools, 2, "应 2 个窗口输出") for _, b := range bools { assert.True(t, b, "had_changed(avg) 首窗与变化窗均应 true") } } -// 窗口内双内联聚合:changed_cols 同时跟踪 avg 和 max。 +// Dual inline aggregation within the window: changed_cols tracks both AVG and MAX simultaneously. func TestCombo_Window_TwoAggregates(t *testing.T) { d := []map[string]any{{"t": 10.0}, {"t": 20.0}, {"t": 30.0}, {"t": 30.0}} got := runWindow(t, `SELECT changed_cols("c_", true, avg(t), max(t)) FROM stream GROUP BY CountingWindow(2)`, d) - // 窗口1 [10,20]: avg=15,max=20(首→都变){c_avg:15,c_max:20} - // 窗口2 [30,30]: avg=30,max=30(都变){c_avg:30,c_max:30} + // Window 1 [10,20]: avg=15, max=20 (both → and first change) {c_avg:15, c_max:20} + // Window 2 [30,30]: avg=30, max=30 (all changes) {c_avg:30, c_max:30} keys := map[string]bool{} for _, r := range got { for k := range r { @@ -560,7 +560,7 @@ func TestCombo_Window_TwoAggregates(t *testing.T) { assert.True(t, keys["c_avg"] && keys["c_max"], "双内联聚合应产出 c_avg 与 c_max 列;got=%v", got) } -// 文档案例 9 镜像:设备级跨窗口变化——changed_col(avg) 按 GROUP BY 分区。 +// Document Case 9 Mirroring: Device-Level Cross-Window Changes — changed_col (avg) partitioned by GROUP BY. func TestCombo_DocCase_GroupedCrossWindow(t *testing.T) { in := []map[string]any{ {"deviceId": "A", "temp": 10.0}, {"deviceId": "A", "temp": 20.0}, @@ -568,7 +568,7 @@ func TestCombo_DocCase_GroupedCrossWindow(t *testing.T) { {"deviceId": "B", "temp": 5.0}, {"deviceId": "B", "temp": 5.0}, } got := runWindow(t, `SELECT deviceId, changed_col(true, avg(temp)) AS chg FROM stream GROUP BY deviceId, CountingWindow(2)`, in) - // A 窗口均值 15,35;B 窗口均值 5。各设备独立:A→{15,35},B→{5}。 + // Window A averages 15.35; Window B averages 5. Each piece of equipment is independent: A→{15,35}, B→{5}. byDev := map[string][]float64{} for _, r := range got { k, _ := r["deviceId"].(string) @@ -583,7 +583,7 @@ func TestCombo_DocCase_GroupedCrossWindow(t *testing.T) { assert.Equal(t, []float64{5}, byDev["B"], "B 设备跨窗口变化") } -// D1:窗口查询里分析函数引用裸原始列 → 报错(不再静默返回列名)。 +// D1: In window queries, the analysis function references the raw column → an error (no longer silently returns column name). func TestCombo_D1_RawColumnInWindowErrors(t *testing.T) { ssql := streamsql.New() defer ssql.Stop() @@ -592,7 +592,7 @@ func TestCombo_D1_RawColumnInWindowErrors(t *testing.T) { assert.Contains(t, err.Error(), "raw column") } -// D9:分析套分析 / 聚合套分析 → 报错;分析套聚合仍允许。 +// D9: Analytical suite analysis / aggregate suite analysis → error report; Analytical set aggregation is still allowed. func TestCombo_D9_NestedAnalyticErrors(t *testing.T) { for _, sql := range []string{ `SELECT lag(lag(a)) AS p FROM stream`, @@ -603,18 +603,18 @@ func TestCombo_D9_NestedAnalyticErrors(t *testing.T) { assert.Error(t, err, "嵌套分析应报错: %s", sql) ssql.Stop() } - // 分析套聚合(窗口内)仍合法。 + // Analytical set aggregation (within the window) remains valid. ok := streamsql.New() defer ok.Stop() assert.NoError(t, ok.Execute(`SELECT changed_cols("t", true, avg(temperature)) FROM stream GROUP BY CountingWindow(2)`)) } -// D5:acc_avg 空累积返回 nil(与 acc_max/min 一致),不返回 0.0。 +// D5: acc_avg Null cumulative returns nil (consistent with acc_max/min), not 0.0. func TestCombo_D5_AccAvgEmptyNil(t *testing.T) { ssql := streamsql.New() require.NoError(t, ssql.Execute(`SELECT acc_avg(s) AS a FROM stream`)) defer ssql.Stop() - // 非数字输入 → count 保持 0 → nil + // Non-numeric input → count remains at 0 → nil r, err := ssql.EmitSync(map[string]any{"s": "not-a-number"}) require.NoError(t, err) assert.Nil(t, r["a"], "空 acc_avg 应返回 nil 而非 0.0") diff --git a/test/e2e/analytic_parity_test.go b/test/e2e/analytic_parity_test.go index d404a34..eeb3806 100644 --- a/test/e2e/analytic_parity_test.go +++ b/test/e2e/analytic_parity_test.go @@ -10,8 +10,8 @@ import ( streamsql "github.com/rulego/streamsql" ) -// 分析函数标准示例集,覆盖变化检测 / lag / latest / 累计 / 条件累计, -// 作为分析函数语法的回归门禁。 +// Standard sample sets of analysis functions, covering change detection / lag / latest +// It serves as a regression access control for analyzing function syntax. func analyticDemo() []map[string]any { return []map[string]any{ @@ -32,7 +32,7 @@ func runWindow(t *testing.T, sql string, inputs []map[string]any) []map[string]a if err := ssql.Execute(sql); err != nil { t.Fatalf("Execute %q: %v", sql, err) } - defer ssql.Stop() // 失败路径兜底(t.Fatalf → Goexit 仍执行);正常路径已显式 Stop,幂等。 + defer ssql.Stop() // Failure Path Bottom Cover (t.Fatalf → Goexit still executed); The normal path has explicitly stopped and explicitly stopped. var mu sync.Mutex var out []map[string]any ssql.AddSink(func(r []map[string]any) { @@ -43,8 +43,8 @@ func runWindow(t *testing.T, sql string, inputs []map[string]any) []map[string]a for _, in := range inputs { ssql.Emit(copyRow(in)) } - time.Sleep(500 * time.Millisecond) // 让窗口触发 + sink 回调执行完毕 - ssql.Stop() // 停止并 join sink worker:在跑回调跑完、不再有并发写 out + time.Sleep(500 * time.Millisecond) // Let the window trigger + sink callback to finish + ssql.Stop() // Stop and join sink worker: After running the callback, no more concurrent writes out mu.Lock() result := out mu.Unlock() @@ -79,7 +79,7 @@ func copyRow(in map[string]any) map[string]any { return out } -// assertRows 按序断言结果行(直接路径结果有序)。 +// assertRows asserts the result rows in order (direct path results are ordered). func assertRows(t *testing.T, sql string, got, want []map[string]any) { t.Helper() if !reflect.DeepEqual(got, want) { @@ -89,10 +89,10 @@ func assertRows(t *testing.T, sql string, got, want []map[string]any) { func TestAnalytic_ChangedCols(t *testing.T) { d := analyticDemo() - // 单列无前缀:首值 + 变化值 + // Single column without prefix: initial value + change value assertRows(t, "E1", runDirect(t, `SELECT changed_cols("", true, temperature) FROM stream`, d), []map[string]any{{"temperature": 23}, {"temperature": 25}}) - // 多列带前缀 + // Many are prefixed with a row of belts assertRows(t, "E2", runDirect(t, `SELECT changed_cols("c_", true, temperature, humidity) FROM stream`, d), []map[string]any{ {"c_temperature": 23, "c_humidity": 88}, @@ -100,7 +100,7 @@ func TestAnalytic_ChangedCols(t *testing.T) { {"c_humidity": 90}, {"c_humidity": 91}, }) - // 不忽略 null + *:每行至少输出变化的 ts + // Do not ignore null + *: output at least the change in ts per line assertRows(t, "E3", runDirect(t, `SELECT changed_cols("c_", false, "*") FROM stream`, d), []map[string]any{ {"c_ts": 1, "c_temperature": 23, "c_humidity": 88}, @@ -129,7 +129,7 @@ func TestAnalytic_HadChanged_Where(t *testing.T) { func TestAnalytic_ChangedCol_AliasAndWhere(t *testing.T) { d := analyticDemo() - // 别名:nil 字段省略、全 nil 行抑制 + // Alias: nil field omitted, full nil row suppression assertRows(t, "E7", runDirect(t, `SELECT changed_col(true, temperature) AS myTemp, changed_col(true, humidity) AS myHum FROM stream`, d), []map[string]any{ {"myTemp": 23, "myHum": 88}, @@ -137,19 +137,19 @@ func TestAnalytic_ChangedCol_AliasAndWhere(t *testing.T) { {"myHum": 90}, {"myHum": 91}, }) - // WHERE 比较 + // WHERE comparison assertRows(t, "E8", runDirect(t, `SELECT ts, temperature, humidity FROM stream WHERE changed_col(true, temperature) > 24`, d), []map[string]any{{"ts": 4, "temperature": 25, "humidity": 88}}) } func TestAnalytic_LagLatest(t *testing.T) { d := analyticDemo() - // lag:首事件无前值 + // lag: The first event has no previous value got := runDirect(t, `SELECT temperature, lag(temperature) AS prev FROM stream`, d) if len(got) != 8 || got[0]["prev"] != nil || got[1]["prev"] != 23 || got[3]["prev"] != 23 { t.Errorf("lag unexpected: %v", got) } - // latest:最新非空值 + // latest: Latest non-null value gotL := runDirect(t, `SELECT latest(temperature) AS lt FROM stream`, d) if len(gotL) != 8 || gotL[0]["lt"] != 23 || gotL[3]["lt"] != 25 { t.Errorf("latest unexpected: %v", gotL) @@ -157,14 +157,14 @@ func TestAnalytic_LagLatest(t *testing.T) { } func TestAnalytic_LagWhenHadChanged(t *testing.T) { - // lag 示例3:WHEN 内嵌 had_changed + 算术 ts - lag(ts,1,ts,true) + // lag Example 3: WHEN embedding had_changed + arithmetic ts - lag(ts,1,ts,true) in := []map[string]any{ {"ts": 1, "Status": "A", "statusCode": 100}, {"ts": 5, "Status": "A", "statusCode": 100}, {"ts": 8, "Status": "B", "statusCode": 200}, {"ts": 12, "Status": "B", "statusCode": 300}, } - // 仅断言可解析执行 + prevStatus 正确(duration 的 WHEN 门控语义见设计文档) + // Only assert that parsable execution + prevStatus is correct (WHEN gating semantics for duration are in the design documentation) got := runDirect(t, `SELECT ts, lag(Status) AS prevStatus, ts - lag(ts, 1, ts, true) OVER (WHEN had_changed(true, statusCode)) AS duration FROM stream`, in) wantPrev := []any{nil, "A", "A", "B"} for i, r := range got { @@ -189,13 +189,13 @@ func TestAnalytic_Acc(t *testing.T) { } func TestAnalytic_AccConditional(t *testing.T) { - // acc_count(a, a>1, a<0):a>1 开始累计,a<0 归零。a:1,2,1,3,-1,1 → 0,1,2,3,0,0 + // acc_count(a, a>1, a<0): a>1 starts accumulation, a<0 resets to zero. a:1,2,1,3,-1,1 → 0,1,2,3,0,0 in := []map[string]any{{"a": 1}, {"a": 2}, {"a": 1}, {"a": 3}, {"a": -1}, {"a": 1}} assertRows(t, "条件 acc_count", runDirect(t, `SELECT acc_count(a, a > 1, a < 0) AS c FROM stream`, in), []map[string]any{{"c": int64(0)}, {"c": int64(1)}, {"c": int64(2)}, {"c": int64(3)}, {"c": int64(0)}, {"c": int64(0)}}) } -// sortedFloatField 从结果行提取某字段的数值并排序(窗口 sink 异步无序,按集合比较)。 +// sortedFloatField extracts the value of a field from the result row and sorts it (window sink is asynchronously unsorted, compared by set). func sortedFloatField(rows []map[string]any, key string) []float64 { var out []float64 for _, r := range rows { @@ -217,14 +217,14 @@ func toFloatVal(v any) float64 { return 0 } -// 分组+分析:分析函数按 GROUP BY 键分区,各分组跨窗口状态独立(不串扰)。 +// Grouping + Analysis: The analysis function is partitioned by the GROUP BY key, with each group independent across window states (no crosstalk). func TestAnalytic_Window_GroupPartition(t *testing.T) { in := []map[string]any{ {"deviceId": "A", "temp": 10.0}, {"deviceId": "B", "temp": 5.0}, {"deviceId": "A", "temp": 20.0}, {"deviceId": "B", "temp": 15.0}, {"deviceId": "A", "temp": 30.0}, } - // CountingWindow(1):每事件一窗。acc_sum(avg(temp)) 跨窗口累计,按 deviceId 分区。 + // CountingWindow(1): One window per event. acc_sum(avg(temp)) Accumulates across windows, partitioned by deviceId. got := runWindow(t, `SELECT deviceId, acc_sum(avg(temp)) AS s FROM stream GROUP BY deviceId, CountingWindow(1)`, in) byDev := map[string][]float64{} for _, r := range got { @@ -234,18 +234,18 @@ func TestAnalytic_Window_GroupPartition(t *testing.T) { for _, v := range byDev { sort.Float64s(v) } - // A: avg=10,20,30 → 累计 10,30,60;B: avg=5,15 → 累计 5,20。分区独立。 + // A: avg=10, 20, 30 → cumulative 10, 30, 60; B: avg=5, 15 → cumulative 5, 20. Independent districts. if len(byDev["A"]) != 3 || byDev["A"][0] != 10 || byDev["A"][1] != 30 || byDev["A"][2] != 60 || len(byDev["B"]) != 2 || byDev["B"][0] != 5 || byDev["B"][1] != 20 { - t.Errorf("分组分区错误: A=%v B=%v (want A=[10,30,60] B=[5,20])", byDev["A"], byDev["B"]) + t.Errorf("Partition error: A=%v B=%v (want A=[10,30,60] B=[5,20])", byDev["A"], byDev["B"]) } } -// E4:窗口内变化检测——changed_cols 包裹 avg,对窗口输出做变化检测。 +// E4: Window Change Detection—changed_cols Wrap AVG to perform change detection on window output. func TestAnalytic_Window_ChangedColsOverAgg(t *testing.T) { d := analyticDemo() - // CountingWindow(2) → 窗口均值 23,24,25,25;changed_cols 跨窗口比较: - // 23(首),24(变),25(变),25(未变→抑制) → tavg:23,24,25 + // CountingWindow(2) → Window Average 23,24,25,25; changed_cols Cross-window comparison: + // 23 (head), 24 (variant), 25 (invariant), 25 (unchanged → suppressed) → tavg:23, 24, 25 got := runWindow(t, `SELECT changed_cols("t", true, avg(temperature)) FROM stream GROUP BY CountingWindow(2)`, d) vals := sortedFloatField(got, "tavg") want := []float64{23, 24, 25} @@ -254,10 +254,10 @@ func TestAnalytic_Window_ChangedColsOverAgg(t *testing.T) { } } -// 窗口内累计——acc_sum 包裹 avg,跨窗口累计求和。 +// Accumulate within the window—acc_sum packet avg, sum across windows. func TestAnalytic_Window_AccSumOverAgg(t *testing.T) { d := analyticDemo() - // 4 窗口 avg=23,24,25,25 → 跨窗口累计求和 23,47,72,97 + // 4 window avg = 23, 24, 25, 25 → cumulative sum across windows 23, 47, 72, 97 got := runWindow(t, `SELECT acc_sum(avg(temperature)) AS s FROM stream GROUP BY CountingWindow(2)`, d) vals := sortedFloatField(got, "s") want := []float64{23, 47, 72, 97} @@ -273,8 +273,8 @@ func TestAnalytic_Window_AccSumOverAgg(t *testing.T) { } } -// acc_sum 直连 vs 窗口:同样数据,结果不同(累积频率/对象/行数均不同)。 -// 直连逐事件累积原始 v;窗口里 acc_sum 必须包裹聚合,对窗口结果跨窗口累积。 +// acc_sum Direct Connection vs. Windows: Same data, different results (cumulative frequency/objects/rows all vary). +// Direct connection of events accumulates the original v; acc_sum in the window must be wrapped and aggregated, and the results of the window must be accumulated across windows. func TestAnalytic_AccSum_WindowVsDirect(t *testing.T) { v := []map[string]any{{"v": 1}, {"v": 2}, {"v": 3}, {"v": 4}} @@ -290,14 +290,14 @@ func TestAnalytic_AccSum_WindowVsDirect(t *testing.T) { wantDirect := []float64{1, 3, 6, 10} wantWin := []float64{3, 10} if !reflect.DeepEqual(directVals, wantDirect) { - t.Errorf("直连 acc_sum(v)=%v want %v", directVals, wantDirect) + t.Errorf("Direct connection acc_sum(v) = %v want %v", directVals, wantDirect) } if !reflect.DeepEqual(winVals, wantWin) { - t.Errorf("窗口 acc_sum(sum(v))=%v want %v", winVals, wantWin) + t.Errorf("Window acc_sum(sum(v))) = %v want %v", winVals, wantWin) } } -// acc_sum 累积状态按实例隔离:两实例交叉 Emit,各自独立累积、互不影响。 +// acc_sum Cumulative State Isolated by Instance: Two instances cross Emit, each independently accumulated without affecting each other. func TestAnalytic_AccSum_InstanceIsolation(t *testing.T) { a := streamsql.New() defer a.Stop() @@ -316,7 +316,7 @@ func TestAnalytic_AccSum_InstanceIsolation(t *testing.T) { t.Fatalf("EmitSync v=%v: %v", v, err) } if r == nil { - t.Fatalf("EmitSync v=%v 返回 nil", v) + t.Fatalf("EmitSync v=%v Returns nil", v) } return r } @@ -326,7 +326,7 @@ func TestAnalytic_AccSum_InstanceIsolation(t *testing.T) { } } - // 交叉 Emit:若累积状态全局共享会互相污染 + // Cross-Emit: If accumulated states are shared globally, they will cause mutual contamination check(emit(a, 1), 1) check(emit(b, 10), 10) check(emit(a, 2), 3) @@ -334,54 +334,54 @@ func TestAnalytic_AccSum_InstanceIsolation(t *testing.T) { check(emit(a, 3), 6) } -// === 解析放行但曾运行期静默错值/空的回归(B1-B4 修复)=== +// === Regression of parse release but previously runtime silence error/empty (B1-B4 fix) === -// B1: 算术表达式包分析函数——分析结果回代入外层表达式再求值。 +// B1: Arithmetic expression package analysis function—the analysis results are substituted back into the outer expression for further evaluation. func TestRuntimeFix_B1_ArithmeticAroundAnalytic(t *testing.T) { d := []map[string]any{ {"k": "d1", "ts": 1}, {"k": "d1", "ts": 2}, {"k": "d1", "ts": 3}, {"k": "d2", "ts": 10}, } - // ts - lag(ts):d1 → [nil,1,1];d2 首行无 lag → nil。 + // ts - lag(ts):d1 → [nil,1,1]; d2 first line has no lag → nil. got := runDirect(t, `SELECT ts - lag(ts) OVER (PARTITION BY k) AS d FROM stream`, d) assertNumericField(t, "B1 ts-lag", got, "d", []any{nil, 1.0, 1.0, nil}) - // 100 - lag(ts):d1 → [nil,99,98];d2 → nil。 + // 100 - lag(ts): d1 → [nil,99,98]; d2 → nil. got = runDirect(t, `SELECT 100 - lag(ts) OVER (PARTITION BY k) AS d FROM stream`, d) assertNumericField(t, "B1 100-lag", got, "d", []any{nil, 99.0, 98.0, nil}) - // 纯分析字段不受回代影响:lag 仍 [nil,1,2,nil]。 + // Pure analysis fields are not affected by reproduction: lag is still [nil,1,2,nil]. got = runDirect(t, `SELECT lag(ts) OVER (PARTITION BY k) AS p FROM stream`, d) assertNumericField(t, "B1 plain lag", got, "p", []any{nil, 1.0, 2.0, nil}) } -// B2: 裸分析函数作 WHERE 条件——值型分析函数走 nil 判定(变化到 0 也要选中)。 +// B2: Use the bare analysis function as a WHERE condition—the value-type analysis function should follow the nil determination (even if it changes to 0, select it). func TestRuntimeFix_B2_BareAnalyticInWhere(t *testing.T) { d := []map[string]any{{"temp": 5}, {"temp": 5}, {"temp": 0}, {"temp": 3}} - // changed_col:变化行(含变化到 0)→ 5,0,3;未变化的第二行被过滤。 + // changed_col: Varied rows (including changes to 0) → 5, 0, 3; the second unchanged row is filtered. got := runDirect(t, `SELECT temp FROM stream WHERE changed_col(true, temp)`, d) assertTempSeq(t, "B2 changed_col", got, []float64{5, 0, 3}) - // 显式 > 0 排除 0 → 5,3(旧行为不变)。 + // Explicitly > 0 excludes 0 → 5,3 (old behavior unchanged). got = runDirect(t, `SELECT temp FROM stream WHERE changed_col(true, temp) > 0`, d) assertTempSeq(t, "B2 changed_col>0", got, []float64{5, 3}) - // had_changed 裸作 WHERE:返回 bool 直判 → 5,0,3。 + // had_changed Raw work WHERE: Return bool direct judgment → 5, 0, 3. got = runDirect(t, `SELECT temp FROM stream WHERE had_changed(true, temp)`, d) assertTempSeq(t, "B2 had_changed", got, []float64{5, 0, 3}) } -// B3: 窗口分析函数参数为"聚合+运算"复合表达式——抽内层聚合、留外层运算符。 +// B3: The window analysis function parameter is a "aggregation + operation" composite expression — extract the inner layer of aggregation and leave the outer layer operators. func TestRuntimeFix_B3_CompositeArgInlineAgg(t *testing.T) { d := []map[string]any{{"temp": 23}, {"temp": 25}, {"temp": 25}, {"temp": 30}} - // CountingWindow(2) → 两窗 avg=24,27.5;avg(temp)+1 → 25,28.5。 + // CountingWindow(2) → Two windows avg=24, 27.5; avg(temp)+1 → 25, 28.5. got := runWindow(t, `SELECT changed_col(true, avg(temp) + 1) AS c FROM stream GROUP BY CountingWindow(2)`, d) if vals := sortedFloatField(got, "c"); !reflect.DeepEqual(vals, []float64{25, 28.5}) { t.Errorf("B3 avg+1: got %v, want [25, 28.5]", vals) } - // 纯 avg(temp) 基线不变 → 24,27.5。 + // Pure AVG (temp) baseline unchanged → 24, 27.5. got = runWindow(t, `SELECT changed_col(true, avg(temp)) AS c FROM stream GROUP BY CountingWindow(2)`, d) if vals := sortedFloatField(got, "c"); !reflect.DeepEqual(vals, []float64{24, 27.5}) { t.Errorf("B3 avg baseline: got %v, want [24, 27.5]", vals) } } -// B4: 窗口分析函数参数用限定列(表.列)——运行期剥前缀解析到 GROUP BY 键值,不返回字面串。 +// B4: Window analysis function parameters use limited columns (tables). column) — Runtime stripping of prefixes to the GROUP BY key does not return literal strings. func TestRuntimeFix_B4_QualifiedColumnArg(t *testing.T) { d := []map[string]any{{"k": "d1"}, {"k": "d1"}, {"k": "d2"}, {"k": "d2"}} got := runWindow(t, `SELECT changed_col(true, stream.k) AS c FROM stream GROUP BY k, CountingWindow(2)`, d) @@ -398,7 +398,7 @@ func TestRuntimeFix_B4_QualifiedColumnArg(t *testing.T) { } } -// assertNumericField 按序断言字段值:want[i]==nil 期望 nil;否则按浮点比较(容 int/float64)。 +// assertNumericField Sequentially asserts field values: want[i]==nil Expectation nil; Otherwise, compare by floating-point (accommodates int/float64). func assertNumericField(t *testing.T, label string, got []map[string]any, key string, want []any) { t.Helper() if len(got) != len(want) { @@ -419,7 +419,7 @@ func assertNumericField(t *testing.T, label string, got []map[string]any, key st } } -// assertTempSeq 按序断言 temp 字段的浮点值序列(直连路径有序)。 +// assertTempSeq asserts the floating-point sequence of temp fields in order (direct path order). func assertTempSeq(t *testing.T, label string, got []map[string]any, want []float64) { t.Helper() vals := make([]float64, 0, len(got)) diff --git a/test/e2e/analytic_runtime_test.go b/test/e2e/analytic_runtime_test.go index 8495b2a..9c03f10 100644 --- a/test/e2e/analytic_runtime_test.go +++ b/test/e2e/analytic_runtime_test.go @@ -9,13 +9,13 @@ import ( "github.com/stretchr/testify/require" ) -// 分析函数 OVER 状态机的运行时特性:并发正确性 + 分区上限 + 性能基准。 -// 并发测试在 CI/Linux 的 -race 下回归(捕获把 lastResults/partitions/state -// 访问移出 fe.mu 的改动);本地普通模式验证无 panic/死锁、增量守恒、上限生效。 +// Analyze the runtime characteristics of the OVER function state machine: concurrency correctness + partition limit + performance benchmark. +// Concurrent tests regress under CI/Linux -race (capture lastResults/partitions/state). +// Changes to the access removal fe.mu); Authenticated in local normal mode, there is no panic/deadlock, incremental conservation, and upper limit effect. -// --- 并发 --- +// --- Concurrent --- -// 多 goroutine 各打一个独立分区:互斥串行化后每分区计数精确,无跨分区串扰。 +// Multiple goroutines each run an independent partition: after mutex serialization, each partition counts accurately, with no cross-partition crosstalk. func TestAnalytic_ConcurrentEmitSync_DistinctPartitions(t *testing.T) { const g, m = 8, 500 ssql := streamsql.New() @@ -49,8 +49,8 @@ func TestAnalytic_ConcurrentEmitSync_DistinctPartitions(t *testing.T) { } } -// 同一分区高并发:所有 Apply 在同一把 fe.mu 下串行,返回值集合恰为 {1..total}, -// 故最大返回值 == total(总事件数),证明增量不丢失。 +// High concurrency within the same partition: All Apply are serialized under the same fe.mu, and the return value set is exactly {1..total}, +// Therefore, the maximum return value == total (total number of events) proves that the increment is not lost. func TestAnalytic_ConcurrentEmitSync_SharedPartition(t *testing.T) { const g, m = 8, 500 const total = g * m @@ -87,10 +87,10 @@ func TestAnalytic_ConcurrentEmitSync_SharedPartition(t *testing.T) { assert.Equal(t, total, gmax, "并发下 acc_count 不得丢增量;max=%d want=%d", gmax, total) } -// --- 分区上限(LRU)--- +// --- Partition Limit (LRU) --- -// WithAnalyticMaxPartitions(2):打 3 个分区后最久未用的 dev1 被淘汰, -// 再来 dev1 状态已重置,acc_count 回到 1(而非 2)。 +// WithAnalyticMaxPartitions(2): After running 3 partitions, the longest-unused dev1 is eliminated, +// Next, the dev1 state has been reset, acc_count return to 1 (instead of 2). func TestAnalytic_MaxPartitions_EvictionResets(t *testing.T) { ssql := streamsql.New(streamsql.WithAnalyticMaxPartitions(2)) require.NoError(t, ssql.Execute( @@ -106,13 +106,13 @@ func TestAnalytic_MaxPartitions_EvictionResets(t *testing.T) { } assert.Equal(t, int64(1), emit("dev1")) assert.Equal(t, int64(1), emit("dev2")) - assert.Equal(t, int64(1), emit("dev3")) // cap=2 → dev1 被淘汰 + assert.Equal(t, int64(1), emit("dev3")) // cap=2 → dev1 is eliminated assert.Equal(t, int64(1), emit("dev1"), "dev1 被淘汰后状态重置,计数回到 1") } -// 默认上限足够大:同样的序列下 dev1 不被淘汰,再来时计数为 2。 +// The default upper limit is large enough: under the same sequence, dev1 is not eliminated, and the count is 2 when it comes back. func TestAnalytic_MaxPartitions_DefaultKeeps(t *testing.T) { - ssql := streamsql.New() // 默认上限 + ssql := streamsql.New() // Default upper limit require.NoError(t, ssql.Execute( `SELECT acc_count(v) OVER (PARTITION BY deviceId) AS c FROM stream`)) defer ssql.Stop() @@ -130,7 +130,7 @@ func TestAnalytic_MaxPartitions_DefaultKeeps(t *testing.T) { assert.Equal(t, int64(2), emit("dev1"), "默认上限下 dev1 状态保留,计数累加到 2") } -// --- 性能基准 --- +// --- Performance Benchmark --- func benchEmitSync(b *testing.B, sql string, row map[string]any) { b.Helper() @@ -139,7 +139,7 @@ func benchEmitSync(b *testing.B, sql string, row map[string]any) { b.Fatalf("execute: %v", err) } defer ssql.Stop() - // 预热(首次 EmitSync 触发 ensureAnalytic 的 sync.Once 初始化)。 + // Preheating (the first EmitSync triggers ensureAnalytic's sync.Once initialized). if _, err := ssql.EmitSync(row); err != nil { b.Fatalf("emit: %v", err) } @@ -150,38 +150,38 @@ func benchEmitSync(b *testing.B, sql string, row map[string]any) { } } -// 基准:无分析函数的普通投影(analytic 引擎走 nil 快路径)。 +// Benchmark: Ordinary projection without analysis functions (analytic engine follows the NIL fast path). func BenchmarkEmitSync_NoAnalytic(b *testing.B) { benchEmitSync(b, `SELECT deviceId, v FROM stream`, map[string]any{"deviceId": "d1", "v": 1}) } -// 分析函数无分区:partitionKey 早返回 "",仅 lag 状态机开销。 +// Parsing functions without partitioning: partitionKey returns "" early, only lags state machine overhead. func BenchmarkAnalytic_NoPartition(b *testing.B) { benchEmitSync(b, `SELECT lag(v) AS p FROM stream`, map[string]any{"deviceId": "d1", "v": 1}) } -// PARTITION BY 单列:每事件构造分区键(类型 switch + Builder)。 +// PARTITION BY Single Column: Constructs partition keys for each event (type switch + Builder). func BenchmarkAnalytic_Partition1Col(b *testing.B) { benchEmitSync(b, `SELECT lag(v) OVER (PARTITION BY deviceId) AS p FROM stream`, map[string]any{"deviceId": "d1", "v": 1}) } -// PARTITION BY 双列:分区键构造 ×2。 +// PARTITION BY Dual Column: Partition Key Construction ×2. func BenchmarkAnalytic_Partition2Col(b *testing.B) { benchEmitSync(b, `SELECT lag(v) OVER (PARTITION BY deviceId, region) AS p FROM stream`, map[string]any{"deviceId": "d1", "region": "z1", "v": 1}) } -// --- 分区语义正确性(边界组合)--- +// --- Semantic correctness of partitions (boundary combinations) --- -// PARTITION BY 在 SELECT 侧的状态隔离:交错输入 d1/d2,各分区 lag 只看本分区前值。 -// 预期 prev = [nil, nil, 10, 100, 20](无隔离会把 d1 的值串进 d2)。 +// PARTITION BY state isolation on the SELECT side: staggered input d1/d2, and the lag of each partition only looks at the previous value of the partition. +// Expect prev = [nil, nil, 10, 100, 20] (without isolation, the value of d1 will be linked to d2). func TestAnalytic_PartitionIsolation_Lag(t *testing.T) { ssql := streamsql.New() require.NoError(t, ssql.Execute("SELECT deviceId, lag(value) OVER (PARTITION BY deviceId) AS prev FROM stream")) @@ -203,8 +203,8 @@ func TestAnalytic_PartitionIsolation_Lag(t *testing.T) { } } -// acc_sum 带 PARTITION BY:各分区独立累加。 -// 预期 total = [10, 100, 30, 300, 60]。 +// acc_sum With PARTITION BY: Each partition is independently accumulated. +// Expected total = [10, 100, 30, 300, 60]. func TestAnalytic_AccSum_Partition(t *testing.T) { ssql := streamsql.New() require.NoError(t, ssql.Execute("SELECT deviceId, acc_sum(value) OVER (PARTITION BY deviceId) AS total FROM stream")) @@ -226,8 +226,8 @@ func TestAnalytic_AccSum_Partition(t *testing.T) { } } -// 同一查询多个分析函数 + PARTITION BY:lag 与 acc_max 各自维护独立状态。 -// 预期 prev = [nil, 10, 5],mx = [10, 10, 20]。 +// Multiple analysis functions + PARTITION BY: lag and acc_max each maintain their own independent state within the same query. +// Expected prev = [nil, 10, 5], mx = [10, 10, 20]. func TestAnalytic_MultipleAnalytic_Partition(t *testing.T) { ssql := streamsql.New() require.NoError(t, ssql.Execute("SELECT deviceId, lag(value) OVER (PARTITION BY deviceId) AS prev, acc_max(value) OVER (PARTITION BY deviceId) AS mx FROM stream")) @@ -249,10 +249,11 @@ func TestAnalytic_MultipleAnalytic_Partition(t *testing.T) { } } -// OVER WHEN × PARTITION BY 组合:每分区独立维护 (历史 + 缓存输出)。 -// WHEN 满足才更新状态并重算 lag、刷新缓存;不满足时返回该分区缓存的上一输出(而非上一个满足值)。 -// 预期 prev = [nil, 20, 20, nil, 20, 30]。 -// row2(5≤15,F) 复用 d1 缓存 20;row4(10≤15,F,d1) 即便前面插了 d2 行,仍取 d1 自己的缓存 20。 +// OVER WHEN × PARTITION BY combination: Each partition is maintained independently (history + cache output). +// Update status and recalculate lag and cache only when WHEN conditions are met; If not met, it returns the previous output of the partition's cache (rather than the previous satisfied value). +// Expected prev = [nil, 20, 20, nil, 20, 30]. +// +// row2(5≤15,F) reuses d1's cache 20; row4(10≤15,F,d1) still takes d1's own cache 20 even if d2 is inserted earlier. func TestAnalytic_WhenAndPartition_Lag(t *testing.T) { ssql := streamsql.New() require.NoError(t, ssql.Execute("SELECT deviceId, lag(value) OVER (PARTITION BY deviceId WHEN value > 15) AS prev FROM stream")) @@ -275,8 +276,8 @@ func TestAnalytic_WhenAndPartition_Lag(t *testing.T) { } } -// acc_avg 带 PARTITION BY:各分区独立累积均值(count 与 sum 都按分区隔离)。 -// 预期 avg = [10, 100, 15, 20, 150]。 +// acc_avg with PARTITION BY: The independent cumulative mean of each partition (count and sum are partitioned separately). +// Expected avg = [10, 100, 15, 20, 150]. func TestAnalytic_AccAvg_Partition(t *testing.T) { ssql := streamsql.New() require.NoError(t, ssql.Execute("SELECT deviceId, acc_avg(value) OVER (PARTITION BY deviceId) AS avg FROM stream")) @@ -298,8 +299,8 @@ func TestAnalytic_AccAvg_Partition(t *testing.T) { } } -// acc_count 带 PARTITION BY:各分区独立计数。 -// 预期 cnt = [1, 1, 2, 3, 2](d1: 1→2→3;d2: 1→2)。 +// acc_count With PARTITION BY: Each partition counts independently. +// Expected CNT = [1, 1, 2, 3, 2](d1: 1→2→3; d2: 1→2). func TestAnalytic_AccCount_Partition(t *testing.T) { ssql := streamsql.New() require.NoError(t, ssql.Execute("SELECT deviceId, acc_count(value) OVER (PARTITION BY deviceId) AS cnt FROM stream")) @@ -321,9 +322,9 @@ func TestAnalytic_AccCount_Partition(t *testing.T) { } } -// lag 多偏移 + 默认值 + ignoreNull 组合:nil 不入历史,历史不足时返回默认值。 -// 序列 [10, 20, nil, 30, 40],预期 lg = [-1.0, -1.0, 10, 10, 20]。 -// 关键判别:row3(nil) 与 row4 都返回 10——nil 被忽略未入历史,故 row4 仍取到 20 之前的 10。 +// lag multi-offset + default + ignoreNull combination: nil does not enter history; returns default value when history is insufficient. +// Sequence [10, 20, nil, 30, 40], expected lg = [-1.0, -1.0, 10, 10, 20]. +// Key determination: Both row3(nil) and row4 return 10—nil is ignored and not historic, so row4 still takes the 10 before 20. func TestAnalytic_LagOffsetDefaultIgnoreNull(t *testing.T) { ssql := streamsql.New() require.NoError(t, ssql.Execute("SELECT lag(value, 2, -1, true) AS lg FROM stream")) diff --git a/test/e2e/analytic_test.go b/test/e2e/analytic_test.go index 18fb67d..a83dc92 100644 --- a/test/e2e/analytic_test.go +++ b/test/e2e/analytic_test.go @@ -9,52 +9,52 @@ import ( "github.com/stretchr/testify/require" ) -// D3:分析函数 alias 与普通列同名 → 编译期报错,不静默覆盖。 +// D3: Analysis function alias shares the same name as the regular column → Compile time error is not silently overridden. func TestAnalytic_AliasCollisionRejected(t *testing.T) { ssql := streamsql.New() err := ssql.Execute("SELECT temperature, lag(temperature) AS temperature FROM stream") require.Error(t, err, "分析函数 alias 与普通列冲突应报错") assert.Contains(t, err.Error(), "duplicate output column") - // 合法别名不受影响。 + // Legal aliases are not affected. ssql2 := streamsql.New() require.NoError(t, ssql2.Execute("SELECT temperature, lag(temperature) AS prev_temp FROM stream")) defer ssql2.Stop() } -// D1:PARTITION BY 键按类型区分,int(1) 与 string("1") 不串台。 +// D1: PARTITION BY key distinguishes by type; int(1) and string("1") are not cross-connected. func TestAnalytic_PartitionKeyTypeSafe(t *testing.T) { ssql := streamsql.New() require.NoError(t, ssql.Execute("SELECT lag(v) OVER (PARTITION BY k) AS prev FROM stream")) defer ssql.Stop() - // k=1(int):首事件 prev=nil + // k=1(int): first event prev=nil r1, _ := ssql.EmitSync(map[string]any{"k": 1, "v": 100}) assert.Nil(t, r1["prev"]) - // k="1"(string):不同分区,prev 仍为 nil(若键冲突会拿到 100) + // k="1"(string): Different partitions, prev still nil (if key conflicts occur, it will get 100) r2, _ := ssql.EmitSync(map[string]any{"k": "1", "v": 200}) assert.Nil(t, r2["prev"], `string "1" 不得与 int 1 共用分区`) - // 回到 k=1(int):prev=100(int 分区状态保留) + // Return k=1(int):p rev=100 (int partition state retained) r3, _ := ssql.EmitSync(map[string]any{"k": 1, "v": 300}) assert.Equal(t, 100, r3["prev"]) } -// changed_cols(prefix, ignoreNull, expr...) 多列动态输出。 -// 仅输出变化列,列名 = prefix + 原列名。 +// changed_cols(prefix, ignoreNull, expr...) multi-column dynamic output. +// Only output variable columns, column name = prefix + original column name. func TestAnalytic_ChangedColsMultiColumn(t *testing.T) { ssql := streamsql.New() require.NoError(t, ssql.Execute(`SELECT changed_cols("c_", true, temperature, humidity) FROM stream`)) defer ssql.Stop() - // 首行:两列都视为变化。 + // First row: Both columns are considered variants. r1, _ := ssql.EmitSync(map[string]any{"temperature": 23, "humidity": 50}) require.NotNil(t, r1) assert.Equal(t, 23, r1["c_temperature"]) assert.Equal(t, 50, r1["c_humidity"]) - // 仅 humidity 变化。 + // Only humidity changes. r2, _ := ssql.EmitSync(map[string]any{"temperature": 23, "humidity": 55}) require.NotNil(t, r2) _, hasTemp := r2["c_temperature"] @@ -62,7 +62,7 @@ func TestAnalytic_ChangedColsMultiColumn(t *testing.T) { assert.Equal(t, 55, r2["c_humidity"]) } -// changed_cols omitEmpty——无变化时抑制整行(changed_cols 为唯一输出)。 +// changed_cols omitEmpty—suppresses entire rows when unchanged (changed_cols is the unique output). func TestAnalytic_ChangedColsOmitEmpty(t *testing.T) { ssql := streamsql.New() require.NoError(t, ssql.Execute(`SELECT changed_cols("c_", true, temperature) FROM stream`)) @@ -72,7 +72,7 @@ func TestAnalytic_ChangedColsOmitEmpty(t *testing.T) { require.NotNil(t, r1) assert.Equal(t, 23, r1["c_temperature"]) - // 未变化 → 整行抑制。 + // Unchanged → Entire line suppression. r2, _ := ssql.EmitSync(map[string]any{"temperature": 23}) assert.Nil(t, r2, "无变化应抑制整行(omitEmpty)") @@ -81,7 +81,7 @@ func TestAnalytic_ChangedColsOmitEmpty(t *testing.T) { assert.Equal(t, 25, r3["c_temperature"]) } -// changed_cols 配普通字段:普通字段总输出,changed_cols 仅变化列;不抑制整行。 +// changed_cols Assign regular fields: total output of regular fields, changed_cols only change columns; Do not suppress the entire line. func TestAnalytic_ChangedColsWithPlainField(t *testing.T) { ssql := streamsql.New() require.NoError(t, ssql.Execute(`SELECT ts, changed_cols("c_", true, temperature) FROM stream`)) @@ -91,7 +91,7 @@ func TestAnalytic_ChangedColsWithPlainField(t *testing.T) { assert.Equal(t, 1, r1["ts"]) assert.Equal(t, 23, r1["c_temperature"]) - // temperature 未变化,但 ts 仍输出(不抑制整行)。 + // Temperature does not change, but ts is still output (does not suppress the entire line). r2, _ := ssql.EmitSync(map[string]any{"ts": 2, "temperature": 23}) require.NotNil(t, r2) assert.Equal(t, 2, r2["ts"]) @@ -99,23 +99,23 @@ func TestAnalytic_ChangedColsWithPlainField(t *testing.T) { assert.False(t, hasC) } -// changed_cols '*':对整行各列检测变化。 +// changed_cols '*': Detects changes in each column of the entire row. func TestAnalytic_ChangedColsStar(t *testing.T) { ssql := streamsql.New() require.NoError(t, ssql.Execute(`SELECT changed_cols("d_", true, *) FROM stream`)) defer ssql.Stop() - // 首行:所有列视为变化。 + // First row: all columns are treated as variables. r1, _ := ssql.EmitSync(map[string]any{"a": 1, "b": 2}) require.NotNil(t, r1) assert.Equal(t, 1, r1["d_a"]) assert.Equal(t, 2, r1["d_b"]) - // 无变化 → 抑制。 + // No change → inhibition. r2, _ := ssql.EmitSync(map[string]any{"a": 1, "b": 2}) assert.Nil(t, r2) - // 仅 a 变化。 + // Only a change. r3, _ := ssql.EmitSync(map[string]any{"a": 9, "b": 2}) require.NotNil(t, r3) assert.Equal(t, 9, r3["d_a"]) @@ -123,7 +123,7 @@ func TestAnalytic_ChangedColsStar(t *testing.T) { assert.False(t, hasB, "b 未变化不应输出") } -// had_changed '*':任一列变化即为 true(CDC 任一变化检测)。 +// had_changed '*': Any change in the column is true (CDC detects any variation). func TestAnalytic_HadChangedStar(t *testing.T) { ssql := streamsql.New() require.NoError(t, ssql.Execute(`SELECT a FROM stream WHERE had_changed(true, *) == true`)) @@ -142,7 +142,7 @@ func TestAnalytic_HadChangedStar(t *testing.T) { assert.Nil(t, r4, "再次无变化") } -// B3:had_changed '*' 在行 schema 变化(列删除)时按列名正确检出变化。 +// B3: had_changed '*' Correctly detects changes by column name when the row schema changes (column deletions). func TestAnalytic_HadChangedStarSchemaDrift(t *testing.T) { ssql := streamsql.New() require.NoError(t, ssql.Execute(`SELECT a FROM stream WHERE had_changed(true, *) == true`)) @@ -151,16 +151,16 @@ func TestAnalytic_HadChangedStarSchemaDrift(t *testing.T) { r1, _ := ssql.EmitSync(map[string]any{"a": 1, "b": 2, "c": 3}) require.NotNil(t, r1, "首行视为变化") - // 列 c 被删除 → 应检出变化(位置比较会漏检)。 + // Column C deleted → should detect changes (position comparison may be missed). r2, _ := ssql.EmitSync(map[string]any{"a": 1, "b": 2}) require.NotNil(t, r2, "列 c 删除应检出变化") - // 再来一条相同的 → 无变化。 + // Another identical → with no change. r3, _ := ssql.EmitSync(map[string]any{"a": 1, "b": 2}) assert.Nil(t, r3, "再次无变化") } -// B13:并发 EmitSync 纯分析查询不应竞态/崩溃(-race 下验证)。 +// B13: Concurrent EmitSync pure analysis queries should not race or crash (verify under -race). func TestAnalytic_ConcurrentEmitSync(t *testing.T) { ssql := streamsql.New() require.NoError(t, ssql.Execute("SELECT lag(v) OVER (PARTITION BY k) AS prev FROM stream")) diff --git a/test/e2e/case_test.go b/test/e2e/case_test.go index 169fb30..9c9fae8 100644 --- a/test/e2e/case_test.go +++ b/test/e2e/case_test.go @@ -11,10 +11,10 @@ import ( "github.com/stretchr/testify/assert" ) -// TestCaseExpressionInSQL 测试CASE表达式在SQL查询中的使用 +// TestCaseExpressionInSQL tests the use of CASE expressions in SQL queries func TestCaseExpressionInSQL(t *testing.T) { t.Parallel() - // 测试非聚合场景中的CASE表达式 + // Test CASE expressions in non-aggregated scenarios sql := `SELECT deviceId, CASE WHEN temperature > 30 THEN 'HOT' WHEN temperature > 20 THEN 'WARM' @@ -23,22 +23,22 @@ func TestCaseExpressionInSQL(t *testing.T) { FROM stream WHERE temperature > 15` - // 创建StreamSQL实例 + // Create a StreamSQL instance streamSQL := streamsql.New() defer streamSQL.Stop() err := streamSQL.Execute(sql) assert.NoError(t, err, "执行SQL应该成功") - // 模拟数据 + // Simulation data testData := []map[string]any{ {"deviceId": "device1", "temperature": 35.0, "status": "active"}, {"deviceId": "device2", "temperature": 25.0, "status": "inactive"}, {"deviceId": "device3", "temperature": 18.0, "status": "active"}, - {"deviceId": "device4", "temperature": 10.0, "status": "inactive"}, // 应该被WHERE过滤掉 + {"deviceId": "device4", "temperature": 10.0, "status": "inactive"}, // It should be filtered out by WHERE (WHERE files). } - // 添加数据并获取结果 + // Add data and get results var results []map[string]any var resultsMutex sync.Mutex streamSQL.Stream().AddSink(func(result []map[string]any) { @@ -51,21 +51,21 @@ func TestCaseExpressionInSQL(t *testing.T) { streamSQL.Emit(data) } - // 等待处理 + // Waiting for processing time.Sleep(100 * time.Millisecond) - // 验证结果 + // Verify the results resultsMutex.Lock() resultCount := len(results) resultsMutex.Unlock() assert.GreaterOrEqual(t, resultCount, 3, "应该有至少3条结果(排除temperature <= 15的记录)") } -// TestCaseExpressionInAggregation 测试CASE表达式在聚合查询中的使用 +// TestCaseExpressionInAggregation tests the use of CASE expressions in aggregated queries func TestCaseExpressionInAggregation(t *testing.T) { t.Parallel() - // 使用处理时间窗口,避免需要推进watermark的复杂性 - // 这个测试主要验证CASE表达式在聚合函数中的使用,而不是事件时间窗口 + // Use a processing time window to avoid the complexity of advancing watermarks + // This test mainly verifies the use of CASE expressions in aggregate functions, rather than event time windows sql := `SELECT deviceId, COUNT(*) as total_count, SUM(CASE WHEN temperature > 30 THEN 1 ELSE 0 END) as hot_count, @@ -73,14 +73,14 @@ func TestCaseExpressionInAggregation(t *testing.T) { FROM stream GROUP BY deviceId, TumblingWindow('1s')` - // 创建StreamSQL实例 + // Create a StreamSQL instance streamSQL := streamsql.New() defer streamSQL.Stop() err := streamSQL.Execute(sql) assert.NoError(t, err, "执行SQL应该成功") - // 模拟数据(不需要时间戳字段,因为使用处理时间窗口) + // Simulated data (no timestamp field required, since processing time window is used) testData := []map[string]any{ {"deviceId": "device1", "temperature": 35.0, "status": "active"}, {"deviceId": "device1", "temperature": 25.0, "status": "inactive"}, @@ -89,7 +89,7 @@ func TestCaseExpressionInAggregation(t *testing.T) { {"deviceId": "device2", "temperature": 22.0, "status": "inactive"}, } - // 添加数据并获取结果 + // Add data and get results var results []map[string]any var resultsMutex sync.Mutex streamSQL.Stream().AddSink(func(result []map[string]any) { @@ -102,22 +102,22 @@ func TestCaseExpressionInAggregation(t *testing.T) { streamSQL.Emit(data) } - // 等待窗口触发 + // Wait for the window to trigger time.Sleep(1200 * time.Millisecond) - // 手动触发窗口 + // Manually trigger the window streamSQL.TriggerWindow() - // 等待结果 + // Wait for the results time.Sleep(100 * time.Millisecond) - // 验证结果 + // Verify the results resultsMutex.Lock() defer resultsMutex.Unlock() assert.Greater(t, len(results), 0, "应该有聚合结果返回") - // 验证结果结构和内容 + // Verify the structure and content of the results deviceResults := make(map[string]map[string]any) for _, result := range results { deviceId, ok := result["deviceId"].(string) @@ -125,62 +125,62 @@ func TestCaseExpressionInAggregation(t *testing.T) { deviceResults[deviceId] = result } - // 期望有两个设备的结果 + // Expect results from two devices assert.Len(t, deviceResults, 2, "应该有两个设备的聚合结果") assert.Contains(t, deviceResults, "device1", "应该包含device1的结果") assert.Contains(t, deviceResults, "device2", "应该包含device2的结果") - // 验证device1的结果 + // Verify the results of device1 device1Result := deviceResults["device1"] - // 基本字段检查 + // Basic field check assert.Contains(t, device1Result, "total_count", "device1结果应该包含total_count") assert.Contains(t, device1Result, "hot_count", "device1结果应该包含hot_count") assert.Contains(t, device1Result, "avg_active_temp", "device1结果应该包含avg_active_temp") - // 详细数值验证 + // Detailed numerical verification totalCount1 := getFloat64Value(device1Result["total_count"]) hotCount1 := getFloat64Value(device1Result["hot_count"]) avgActiveTemp1 := getFloat64Value(device1Result["avg_active_temp"]) - // device1: 3条记录总数 + // device1: Total number of 3 records assert.Equal(t, 3.0, totalCount1, "device1应该有3条记录") - // device1: 2条高温记录 (35.0 > 30, 32.0 > 30) + // device1: 2 high-temperature records (35.0 > 30, 32.0 > 30) assert.Equal(t, 2.0, hotCount1, "device1应该有2条高温记录") - // device1: active状态的平均温度 (35.0 + 0 + 32.0) / 3 = 22.333... + // device1: Average temperature in active state (35.0 + 0 + 32.0) / 3 = 22.333... expectedActiveAvg := (35.0 + 0 + 32.0) / 3.0 assert.InDelta(t, expectedActiveAvg, avgActiveTemp1, 0.01, "device1的AVG(CASE WHEN...)应该正确计算") - // 验证device2的结果 + // Verify the results of device2 device2Result := deviceResults["device2"] - // 基本字段检查 + // Basic field check assert.Contains(t, device2Result, "total_count", "device2结果应该包含total_count") assert.Contains(t, device2Result, "hot_count", "device2结果应该包含hot_count") assert.Contains(t, device2Result, "avg_active_temp", "device2结果应该包含avg_active_temp") - // 详细数值验证 + // Detailed numerical verification totalCount2 := getFloat64Value(device2Result["total_count"]) hotCount2 := getFloat64Value(device2Result["hot_count"]) avgActiveTemp2 := getFloat64Value(device2Result["avg_active_temp"]) - // device2: 2条记录总数 + // device2: Total number of 2 records assert.Equal(t, 2.0, totalCount2, "device2应该有2条记录") - // device2: 0条高温记录 (没有温度>30的) + // device2: 0 high-temperature records (no temperature >30) assert.Equal(t, 0.0, hotCount2, "device2应该有0条高温记录") // device2: CASE WHEN status='active' THEN temperature ELSE 0 - // 28.0 (active) + 0 (inactive) = 28.0, 平均值 = (28.0 + 0) / 2 = 14.0 + // 28.0 (active) + 0 (inactive) = 28.0, mean = (28.0 + 0) / 2 = 14.0 expectedActiveAvg2 := (28.0 + 0) / 2.0 assert.InDelta(t, expectedActiveAvg2, avgActiveTemp2, 0.01, "device2的AVG(CASE WHEN...)应该正确计算") } -// getFloat64Value 辅助函数,将any转换为float64 +// getFloat64Value auxiliary function to convert any to float64 func getFloat64Value(value any) float64 { switch v := value.(type) { case float64: @@ -196,7 +196,7 @@ func getFloat64Value(value any) float64 { } } -// TestComplexCaseExpressionsInAggregation 测试复杂CASE表达式在聚合查询中的使用 +// TestComplexCaseExpressionsInAggregation: Tests the use of complex CASE expressions in aggregated queries func TestComplexCaseExpressionsInAggregation(t *testing.T) { t.Parallel() testCases := []struct { @@ -229,7 +229,7 @@ func TestComplexCaseExpressionsInAggregation(t *testing.T) { data: []map[string]any{ {"deviceId": "device1", "temperature": 23.0}, {"deviceId": "device1", "temperature": 27.0}, - {"deviceId": "device1", "temperature": 35.0}, // 这个会被排除 + {"deviceId": "device1", "temperature": 35.0}, // This will be ruled out }, description: "测试带函数的CASE表达式在AVG聚合中的使用", }, @@ -250,14 +250,14 @@ func TestComplexCaseExpressionsInAggregation(t *testing.T) { for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { - // 创建StreamSQL实例 + // Create a StreamSQL instance streamSQL := streamsql.New() defer streamSQL.Stop() err := streamSQL.Execute(tc.sql) assert.NoError(t, err, "执行SQL应该成功") - // 添加数据并获取结果 + // Add data and get results var results []map[string]any var resultsMutex sync.Mutex streamSQL.Stream().AddSink(func(result []map[string]any) { @@ -270,16 +270,16 @@ func TestComplexCaseExpressionsInAggregation(t *testing.T) { streamSQL.Emit(data) } - // 等待窗口触发 + // Wait for the window to trigger time.Sleep(1200 * time.Millisecond) - // 手动触发窗口 + // Manually trigger the window streamSQL.TriggerWindow() - // 等待结果 + // Wait for the results time.Sleep(100 * time.Millisecond) - // 验证至少有结果返回 + // At least verification yields results resultsMutex.Lock() hasResults := len(results) > 0 resultsMutex.Unlock() @@ -288,14 +288,14 @@ func TestComplexCaseExpressionsInAggregation(t *testing.T) { } } -// TestCaseExpressionNonAggregated 测试非聚合场景下的CASE表达式 +// TestCaseExpressionNonAggregated Tests CASE expressions in non-aggregated scenarios func TestCaseExpressionNonAggregated(t *testing.T) { t.Parallel() tests := []struct { name string sql string testData []map[string]any - expected []map[string]any // 期望的结果 + expected []map[string]any // The expected result wantErr bool }{ { @@ -383,10 +383,10 @@ func TestCaseExpressionNonAggregated(t *testing.T) { return } - // 如果执行成功,继续测试数据处理 + // If successful, continue testing data processing strm := ssql.Stream() - // 收集所有结果 + // Collect all the results var allResults []map[string]any resultChan := make(chan []map[string]any, 10) strm.AddSink(func(result []map[string]any) { @@ -396,16 +396,16 @@ func TestCaseExpressionNonAggregated(t *testing.T) { } }) - // 添加测试数据 + // Add test data for _, data := range tt.testData { strm.Emit(data) } - // 等待并收集结果 + // Wait and collect results ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) defer cancel() - // 收集所有结果 + // Collect all the results for i := 0; i < len(tt.testData); i++ { select { case result := <-resultChan: @@ -418,10 +418,10 @@ func TestCaseExpressionNonAggregated(t *testing.T) { } } - // 验证结果数量 + // Verification of the number of results assert.Equal(t, len(tt.expected), len(allResults), "结果数量不匹配") - // 验证每个结果的内容(不依赖顺序) + // Verify the content of each result (not based on order) expectedMap := make(map[string]map[string]any) for _, expected := range tt.expected { deviceId, ok := expected["deviceId"].(string) @@ -430,7 +430,7 @@ func TestCaseExpressionNonAggregated(t *testing.T) { } } - // 验证所有期望的设备都出现在结果中 + // All the devices you expect to verify appear in the results for deviceId := range expectedMap { found := false for _, actual := range allResults { @@ -445,22 +445,22 @@ func TestCaseExpressionNonAggregated(t *testing.T) { for _, actual := range allResults { deviceId, ok := actual["deviceId"].(string) if !ok { - t.Errorf("结果中缺少deviceId字段") + t.Errorf("deviceId fields are missing from the results") continue } expected, exists := expectedMap[deviceId] if !exists { - t.Errorf("未找到设备 %s 的期望结果", deviceId) + t.Errorf("Expected results for device %s not found", deviceId) continue } - // 验证每个字段 + // Verify each field for key, expectedValue := range expected { actualValue, exists := actual[key] assert.True(t, exists, "字段 %s 不存在于结果中", key) if exists { - // 对于数值类型,使用近似比较 + // For numeric types, approximate comparison is used if expectedFloat, ok := expectedValue.(float64); ok { if actualFloat, ok := actualValue.(float64); ok { assert.InDelta(t, expectedFloat, actualFloat, 0.001, "字段 %s 的值不匹配", key) @@ -473,7 +473,7 @@ func TestCaseExpressionNonAggregated(t *testing.T) { } } - // 验证结果中没有多余的字段(除了deviceId) + // There are no extra fields in the validation results (except deviceId) for key := range actual { if key == "deviceId" { continue @@ -482,13 +482,13 @@ func TestCaseExpressionNonAggregated(t *testing.T) { assert.True(t, exists, "结果中包含未期望的字段 %s", key) } - t.Logf("设备 %s: 期望=%v, 实际=%v", deviceId, expected, actual) + t.Logf("Equipment %s: Expected = %v, Actual = %v", deviceId, expected, actual) } }) } } -// TestCaseExpressionAggregated 测试聚合场景下的CASE表达式 +// TestCaseExpressionAggregated tests the CASE expression in the aggregation scenario func TestCaseExpressionAggregated(t *testing.T) { t.Parallel() tests := []struct { @@ -558,7 +558,7 @@ func TestCaseExpressionAggregated(t *testing.T) { strm := ssql.Stream() - // 使用通道等待结果,避免固定等待时间 + // Use channels to wait for results and avoid fixed waiting times resultChan := make(chan any, 5) strm.AddSink(func(result []map[string]any) { select { @@ -571,37 +571,37 @@ func TestCaseExpressionAggregated(t *testing.T) { strm.Emit(data) } - // 使用带超时的等待机制 + // Use a wait-out mechanism with timeouts ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) defer cancel() var results []map[string]any - // 等待窗口触发或超时 + // Wait for the window to trigger or time out select { case result := <-resultChan: if resultSlice, ok := result.([]map[string]any); ok { results = append(results, resultSlice...) } case <-time.After(1200 * time.Millisecond): - // 如果1.2秒内没有结果,手动触发窗口 + // If there is no result within 1.2 seconds, manually trigger the window if strm.Window != nil { ssql.TriggerWindow() } - // 再等待一点时间获取结果 + // Wait a little longer for the results select { case result := <-resultChan: if resultSlice, ok := result.([]map[string]any); ok { results = append(results, resultSlice...) } case <-time.After(200 * time.Millisecond): - // 超时,继续验证 + // Timeout, continue verification } case <-ctx.Done(): return } - // 验证结果 + // Verify the results if len(results) > 0 { firstResult := results[0] assert.NotNil(t, firstResult) @@ -611,7 +611,7 @@ func TestCaseExpressionAggregated(t *testing.T) { } } -// TestCaseExpressionNullHandlingInAggregation 测试CASE表达式在聚合函数中正确处理NULL值 +// TestCaseExpressionNullHandlingInAggregation: Tests whether the CASE expression correctly handles NULL values in the aggregation function func TestCaseExpressionNullHandlingInAggregation(t *testing.T) { t.Parallel() testCases := []struct { @@ -631,24 +631,24 @@ func TestCaseExpressionNullHandlingInAggregation(t *testing.T) { FROM stream GROUP BY deviceType, TumblingWindow('2s')`, testData: []map[string]any{ - {"deviceType": "sensor", "temperature": 35.0}, // 满足条件 - {"deviceType": "sensor", "temperature": 25.0}, // 不满足条件,返回NULL - {"deviceType": "sensor", "temperature": 32.0}, // 满足条件 - {"deviceType": "monitor", "temperature": 28.0}, // 不满足条件,返回NULL - {"deviceType": "monitor", "temperature": 33.0}, // 满足条件 + {"deviceType": "sensor", "temperature": 35.0}, // Conditions are met + {"deviceType": "sensor", "temperature": 25.0}, // If the condition is not met, return NULL + {"deviceType": "sensor", "temperature": 32.0}, // Conditions are met + {"deviceType": "monitor", "temperature": 28.0}, // If the condition is not met, return NULL + {"deviceType": "monitor", "temperature": 33.0}, // Conditions are met }, expectedDeviceResults: map[string]map[string]any{ "sensor": { "high_temp_sum": 67.0, // 35 + 32 - "high_temp_count": 2.0, // COUNT应该忽略NULL + "high_temp_count": 2.0, // COUNT should ignore NULL "high_temp_avg": 33.5, // (35 + 32) / 2 - "total_count": 3.0, // 总记录数 + "total_count": 3.0, // Total recorded count }, "monitor": { - "high_temp_sum": 33.0, // 只有33 - "high_temp_count": 1.0, // COUNT应该忽略NULL - "high_temp_avg": 33.0, // 只有33 - "total_count": 2.0, // 总记录数 + "high_temp_sum": 33.0, // Only 33 + "high_temp_count": 1.0, // COUNT should ignore NULL + "high_temp_avg": 33.0, // Only 33 + "total_count": 2.0, // Total recorded count }, }, description: "验证CASE表达式返回的NULL值被聚合函数正确忽略", @@ -663,16 +663,16 @@ func TestCaseExpressionNullHandlingInAggregation(t *testing.T) { FROM stream GROUP BY deviceType, TumblingWindow('2s')`, testData: []map[string]any{ - {"deviceType": "cold_sensor", "temperature": 20.0}, // 不满足条件 - {"deviceType": "cold_sensor", "temperature": 25.0}, // 不满足条件 - {"deviceType": "cold_sensor", "temperature": 30.0}, // 不满足条件 + {"deviceType": "cold_sensor", "temperature": 20.0}, // The conditions are not met + {"deviceType": "cold_sensor", "temperature": 25.0}, // The conditions are not met + {"deviceType": "cold_sensor", "temperature": 30.0}, // The conditions are not met }, expectedDeviceResults: map[string]map[string]any{ "cold_sensor": { - "impossible_sum": nil, // 全NULL时SUM应返回NULL - "impossible_count": 0.0, // COUNT应返回0 - "impossible_avg": nil, // 全NULL时AVG应返回NULL - "total_count": 3.0, // 总记录数 + "impossible_sum": nil, // For full NULL, the SUM should return NULL + "impossible_count": 0.0, // COUNT should return 0 + "impossible_avg": nil, // For full NULL, AVG should return NULL + "total_count": 3.0, // Total recorded count }, }, description: "验证当CASE表达式全部返回NULL时,聚合函数的正确行为", @@ -681,15 +681,15 @@ func TestCaseExpressionNullHandlingInAggregation(t *testing.T) { for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { - // 创建StreamSQL实例 + // Create a StreamSQL instance ssql := streamsql.New() defer ssql.Stop() - // 执行SQL + // Execute SQL err := ssql.Execute(tc.sql) assert.NoError(t, err, "SQL执行应该成功") - // 收集结果 + // Collect the results var results []map[string]any resultChan := make(chan any, 10) @@ -697,15 +697,15 @@ func TestCaseExpressionNullHandlingInAggregation(t *testing.T) { resultChan <- result }) - // 添加测试数据 + // Add test data for _, data := range tc.testData { ssql.Stream().Emit(data) } - // 等待窗口触发 + // Wait for the window to trigger time.Sleep(3 * time.Second) - // 收集结果 + // Collect the results collecting: for { select { @@ -718,17 +718,17 @@ func TestCaseExpressionNullHandlingInAggregation(t *testing.T) { } } - // 验证结果数量 + // Verification of the number of results assert.Len(t, results, len(tc.expectedDeviceResults), "结果数量应该匹配") - // 验证各个deviceType的结果 + // Verify the results of each deviceType for _, result := range results { deviceType := result["deviceType"].(string) expected := tc.expectedDeviceResults[deviceType] assert.NotNil(t, expected, "应该有设备类型 %s 的期望结果", deviceType) - // 验证每个字段 + // Verify each field for key, expectedValue := range expected { if key == "deviceType" { continue @@ -736,7 +736,7 @@ func TestCaseExpressionNullHandlingInAggregation(t *testing.T) { actualValue := result[key] - // 处理NULL值比较 + // Handle NULL value comparison if expectedValue == nil { assert.Nil(t, actualValue, "设备类型 %s 的字段 %s 应该为NULL", deviceType, key) @@ -750,7 +750,7 @@ func TestCaseExpressionNullHandlingInAggregation(t *testing.T) { } } -// TestHavingWithCaseExpression 测试HAVING子句中的CASE表达式 +// TestHavingWithCaseExpression tests the CASE expression in the HAVING clause func TestHavingWithCaseExpression(t *testing.T) { t.Parallel() tests := []struct { @@ -787,7 +787,7 @@ func TestHavingWithCaseExpression(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - // 测试SQL解析 + // Test SQL parsing _, err := rsql.NewParser(tt.sql).Parse() if tt.wantErr { @@ -799,21 +799,21 @@ func TestHavingWithCaseExpression(t *testing.T) { assert.NoError(t, err, "SQL解析应该成功") } - // 如果解析成功,尝试创建StreamSQL实例 + // If the parsing succeeds, try creating a StreamSQL instance if !tt.wantErr && err == nil { streamSQL := streamsql.New() defer streamSQL.Stop() err = streamSQL.Execute(tt.sql) if err != nil { - t.Skipf("HAVING中的CASE表达式执行暂不支持: %v", err) + t.Skipf("CASE expression execution in HAVING is not currently supported: %v", err) } } }) } } -// TestHavingWithCaseExpressionFunctional 功能测试HAVING子句中的CASE表达式 +// TestHavingWithCaseExpressionFunctional Function tests CASE expressions in HAVING clauses func TestHavingWithCaseExpressionFunctional(t *testing.T) { t.Parallel() sql := `SELECT deviceId, @@ -824,33 +824,33 @@ func TestHavingWithCaseExpressionFunctional(t *testing.T) { GROUP BY deviceId, TumblingWindow('2s') HAVING hot_count >= 2` - // 创建StreamSQL实例 + // Create a StreamSQL instance streamSQL := streamsql.New() defer streamSQL.Stop() err := streamSQL.Execute(sql) assert.NoError(t, err, "执行SQL应该成功") - // 模拟数据 + // Simulation data testData := []map[string]any{ - // device1: 3条高温记录,应该通过HAVING条件 + // device1: 3 high-temperature records, should pass HAVING conditions {"deviceId": "device1", "temperature": 35.0}, {"deviceId": "device1", "temperature": 32.0}, {"deviceId": "device1", "temperature": 31.0}, - {"deviceId": "device1", "temperature": 25.0}, // 不是高温 + {"deviceId": "device1", "temperature": 25.0}, // Not high temperatures - // device2: 1条高温记录,不应该通过HAVING条件 + // device2: One high-temperature record, should not pass HAVING conditions {"deviceId": "device2", "temperature": 33.0}, {"deviceId": "device2", "temperature": 28.0}, {"deviceId": "device2", "temperature": 26.0}, - // device3: 2条高温记录,应该通过HAVING条件 + // device3: Two high-temperature records, should pass HAVING conditions {"deviceId": "device3", "temperature": 34.0}, {"deviceId": "device3", "temperature": 31.0}, {"deviceId": "device3", "temperature": 29.0}, } - // 添加数据并获取结果 + // Add data and get results var results []map[string]any var resultsMutex sync.Mutex streamSQL.Stream().AddSink(func(result []map[string]any) { @@ -863,23 +863,23 @@ func TestHavingWithCaseExpressionFunctional(t *testing.T) { streamSQL.Emit(data) } - // 等待窗口触发 + // Wait for the window to trigger time.Sleep(2500 * time.Millisecond) - // 手动触发窗口 + // Manually trigger the window streamSQL.TriggerWindow() - // 等待结果 + // Wait for the results time.Sleep(200 * time.Millisecond) - // 验证结果 + // Verify the results resultsMutex.Lock() defer resultsMutex.Unlock() - // 应该只有device1和device3通过HAVING条件(hot_count >= 2) + // Only device1 and device3 should pass the HAVING condition (hot_count > = 2) assert.Greater(t, len(results), 0, "应该有结果返回") - // 验证结果中只包含满足HAVING条件的设备 + // Only devices that meet the HAVING criteria are included in the validation results deviceResults := make(map[string]map[string]any) for _, result := range results { deviceId, ok := result["deviceId"].(string) @@ -887,23 +887,23 @@ func TestHavingWithCaseExpressionFunctional(t *testing.T) { deviceResults[deviceId] = result } - // 验证HAVING条件的过滤效果 + // Verify the filtering effect of HAVING conditions for deviceId, result := range deviceResults { hotCount := getFloat64Value(result["hot_count"]) assert.GreaterOrEqual(t, hotCount, 2.0, "设备 %s 的hot_count应该 >= 2 (HAVING条件)", deviceId) } - // device2应该被HAVING条件过滤掉(只有1条高温记录 < 2) + // device2 should be filtered out by HAVING conditions (only 1 high-temperature record < 2) assert.NotContains(t, deviceResults, "device2", "device2应该被HAVING条件过滤掉(hot_count=1 < 2)") - // 验证期望的设备出现在结果中 + // The device that verifies the desired results appears in the results assert.Contains(t, deviceResults, "device1", "device1应该通过HAVING条件") assert.Contains(t, deviceResults, "device3", "device3应该通过HAVING条件") } -// TestNegativeNumberInSQL 测试负数在完整SQL中的使用 +// TestNegativeNumberInSQL tests the use of negative numbers in full SQL func TestNegativeNumberInSQL(t *testing.T) { t.Parallel() sql := `SELECT deviceId, @@ -926,7 +926,7 @@ func TestNegativeNumberInSQL(t *testing.T) { err := streamSQL.Execute(sql) assert.NoError(t, err, "包含负数的SQL应该执行成功") - // 模拟包含负数的数据 + // The simulation contains negative data testData := []map[string]any{ {"deviceId": "sensor1", "temperature": -15.0}, {"deviceId": "sensor2", "temperature": -5.0}, @@ -934,7 +934,7 @@ func TestNegativeNumberInSQL(t *testing.T) { {"deviceId": "sensor4", "temperature": 10.0}, } - // 收集结果 + // Collect the results var results []map[string]any var resultsMutex sync.Mutex @@ -944,20 +944,20 @@ func TestNegativeNumberInSQL(t *testing.T) { results = append(results, result...) }) - // 添加测试数据 + // Add test data for _, data := range testData { streamSQL.Emit(data) } - // 等待处理 + // Waiting for processing time.Sleep(200 * time.Millisecond) - // 验证结果 + // Verify the results resultsMutex.Lock() defer resultsMutex.Unlock() for _, result := range results { - // 验证包含必要字段 + // Verify that the required fields are included assert.Contains(t, result, "deviceId", "结果应该包含deviceId") assert.Contains(t, result, "temperature", "结果应该包含temperature") assert.Contains(t, result, "temp_category", "结果应该包含temp_category") diff --git a/test/e2e/cep_doc_verify_test.go b/test/e2e/cep_doc_verify_test.go index c53367d..497118b 100644 --- a/test/e2e/cep_doc_verify_test.go +++ b/test/e2e/cep_doc_verify_test.go @@ -2,10 +2,10 @@ package e2e import "testing" -// 验证《模式识别》文档(rulego-doc/03.StreamSQL/15.模式识别.md + 31.案例集锦/07)的 -// 场景 SQL 可 Execute 且匹配数符合文档预期,防文档 SQL 与实现漂移。每场景对应文档一个子场景。 +// Verify the "Pattern Recognition" documentation (rulego-doc/03.StreamSQL/15.patternrecognition.md + 31. Case Collection/07). +// Scenario SQL can be executed with matching numbers that meet document expectations, preventing document SQL and implementation drift. Each scenario corresponds to a sub-scenario in the document. -// 场景 A:连续越限防抖 A{3}。 +// Scenario A: Continuous Over-limit Stabilization A{3}. func TestDocCEP_A_ConsecutiveThreshold(t *testing.T) { sql := `SELECT * FROM stream MATCH_RECOGNIZE ( @@ -30,7 +30,7 @@ func TestDocCEP_A_ConsecutiveThreshold(t *testing.T) { } } -// 场景 B:升温后骤降 A+ B(符号限定字段 B.temp + 聚合 MAX(A.temp))。 +// Scenario B: Sudden drop after heating A+ B (symbol constraint field B.temp + aggregation MAX (A.temp)). func TestDocCEP_B_RiseThenDrop(t *testing.T) { sql := `SELECT * FROM stream MATCH_RECOGNIZE ( @@ -53,7 +53,7 @@ func TestDocCEP_B_RiseThenDrop(t *testing.T) { } } -// 场景 C:振动突发 A{5,}。 +// Scenario C: Vibration Burst A{5,}. func TestDocCEP_C_VibrationBurst(t *testing.T) { sql := `SELECT * FROM stream MATCH_RECOGNIZE ( @@ -79,7 +79,7 @@ func TestDocCEP_C_VibrationBurst(t *testing.T) { } } -// 场景 D:开停机工作流——多字符符号名 Start/Running/Stop。 +// Scenario D: Start/Stop Workflow — Multi-character Symbol Name Start/Running/Stop. func TestDocCEP_D_StartRunningStop(t *testing.T) { sql := `SELECT * FROM stream MATCH_RECOGNIZE ( @@ -102,7 +102,7 @@ func TestDocCEP_D_StartRunningStop(t *testing.T) { } } -// 场景 E:乱序鉴权 PERMUTE(Login, Auth),两会话各一次 = 2。 +// Scenario E: Out-of-order authentication PERMUTE(Login, Auth), two sessions each = 2. func TestDocCEP_E_PermuteAuth(t *testing.T) { sql := `SELECT * FROM stream MATCH_RECOGNIZE ( @@ -125,7 +125,7 @@ func TestDocCEP_E_PermuteAuth(t *testing.T) { } } -// 场景 F:时间约束——Alert 后 30s 内 Ack。dev-01 间隔 10s 匹配,dev-02 间隔 60s 超窗。 +// Scenario F: Time constraint—Ack within 30 seconds after the Alert. dev-01 matches every 10 seconds, dev-02 every 60 seconds for window overshoot. func TestDocCEP_F_WithinConfirm(t *testing.T) { sql := `SELECT * FROM stream MATCH_RECOGNIZE ( diff --git a/test/e2e/cep_stress_test.go b/test/e2e/cep_stress_test.go index 33aeb32..b85b7df 100644 --- a/test/e2e/cep_stress_test.go +++ b/test/e2e/cep_stress_test.go @@ -10,11 +10,11 @@ import ( "github.com/stretchr/testify/require" ) -// CEP 压力测试(streamsql 全链路层):MATCH_RECOGNIZE 经 Emit→async→CEP→sink 在持续负载下的 -// 稳定性——sweeper 不泄漏、分区 LRU 回收、无 panic。与 stress_test.go 同构(Create/Stop 循环、 -// 持续负载堆稳定、分区驱逐),但 CEP 必须用 Emit(async,EmitSync 拒绝 MATCH_RECOGNIZE), -// 故用 sync sink 计数做 drain 同步点:PATTERN(A) 每事件 1 匹配,wantMatches=事件数。 -// WITHIN '1h' 让 sweeper 启动(测其经 stream.Stop 正确 join)。本地普通模式跑(-race 由 CI/Linux 回归)。 +// CEP stress testing (streamsql full-link layer): MATCH_RECOGNIZE → emit→ sink under continuous load via CEP→ +// Stability—no sweeper leakage, partitioned LRU recovery, no panic. Isomorphic with stress_test.go (Create/Stop loop, +// Continuous load heap stability, partition eviction), but CEP must use Emit (async, EmitSync denies MATCH_RECOGNIZE), +// Therefore, use sync sink counts to create drain synchronization points: PATTERN(A) matches 1 per event, wantMatches = number of events. +// WITHIN '1h' Start the sweeper (test its stream.Stop join correctly). Local normal mode runs (-race reverted from CI/Linux). const cepStressSQL = `SELECT * FROM stream MATCH_RECOGNIZE ( @@ -25,10 +25,10 @@ const cepStressSQL = `SELECT * FROM stream WITHIN '1h' )` -// drainCepMatches 分批灌 rows(async Emit),每批等 sync sink 计数追上再灌下一批(背压)。 -// 裸连灌会撑满 dataChan 触发 Emit 丢弃(非阻塞满则丢,WARN "Data channel is full"), -// 导致 wantMatches 永远到不了 → 超时。背压让 Emit 速率跟随 processor,channel 不满不丢; -// PATTERN(A) 每事件 1 匹配,sink 计数即已处理事件数。 +// drainCepMatches loads rows (async emit) in batches, and after each batch the sync sink count catches up, the next batch (back pressure) is added. +// Continuous bare loading will fill dataChan to trigger emit discard (if not full, discard it, WARN "Data channel is full"), +// This caused wantMatches to never reach → timeout. Backpressure causes Emit rates to follow the processor, so channels are not lost when not satisfied; +// PATTERN(A) matches 1 event per event, and the sink count is the number of processed events. func drainCepMatches(t testing.TB, s *streamsql.Streamsql, rows []map[string]any) time.Duration { t.Helper() var got int64 @@ -43,12 +43,12 @@ func drainCepMatches(t testing.TB, s *streamsql.Streamsql, rows []map[string]any for j := i; j < end; j++ { s.Emit(rows[j]) } - // 等本批匹配产出(processor 处理完入队的)再灌下一批。 + // Wait for the current batch to match output (processor processing and join the queue) before loading the next batch. target := int64(end) dl := time.Now().Add(120 * time.Second) for atomic.LoadInt64(&got) < target { if time.Now().After(dl) { - t.Fatalf("drain 背压超时:got=%d want=%d", atomic.LoadInt64(&got), target) + t.Fatalf("drain Backpressure timeout: got = %d want = %d", atomic.LoadInt64(&got), target) } runtime.Gosched() } @@ -56,11 +56,11 @@ func drainCepMatches(t testing.TB, s *streamsql.Streamsql, rows []map[string]any return time.Since(start) } -// --- 1. Create/Stop 循环无 goroutine 泄漏 --- +// --- 1. Create/Stop loop without goroutine leakage --- -// 30 轮 New→Execute(WITHIN 触发 sweeper)→Emit→Stop。每轮 Stop 应回收 sweeper goroutine + -// 数据处理 goroutine,NumGoroutine 回到基线。捕获「sweeper 未被 stream.Stop join」型泄漏: -// 每轮残留 k → 末值 base+cycles*k。 +// 30 rounds New→Execute (WITHIN triggers sweeper) →Emit→Stop. Each round of Stop should reclaim sweeper goroutine + +// Data processing goroutine, NumGoroutine returns to baseline. Capturing the "sweeper not stream.Stop join" type leak: +// Each round retains k → final value base+cycles * k. func TestStressCEP_NoGoroutineLeak_CreateStop(t *testing.T) { runtime.GC() base := runtime.NumGoroutine() @@ -77,7 +77,7 @@ func TestStressCEP_NoGoroutineLeak_CreateStop(t *testing.T) { s.Stop() } - // Stop 为 grace join,给余量等残留协程退出后采样。 + // Stop is grace join, which samples residual coroutines such as margins after exiting. deadline := time.Now().Add(3 * time.Second) var final int for { @@ -96,16 +96,16 @@ func TestStressCEP_NoGoroutineLeak_CreateStop(t *testing.T) { t.Logf("goroutine: base=%d final=%d (cycles=%d)", base, final, cycles) } -// --- 2. 持续负载堆稳定 --- +// --- 2. Continuous load stack stability--- -// 单实例持续 10 万事件(50 分区):堆增量受控、全程无 panic。堆增量若随事件数线性增长, -// 疑为 partition/输出 map 等状态留存型泄漏。 +// Single instance sustains 100,000 events (50 partitions): heap increment is controlled with no panic throughout. If the pile increment grows linearly with the number of events, +// Suspected to be state-retained leaks such as partition/output map. func TestStressCEP_SustainedLoad_HeapStable(t *testing.T) { s := streamsql.New(streamsql.WithBufferSizes(4096, 1024, 256)) require.NoError(t, s.Execute(cepStressSQL)) defer s.Stop() - // 预热(触发分区/sweeper 初始化),再取基线。 + // Preheat (trigger partition/sweeper initialization), then take the baseline. drainCepMatches(t, s, []map[string]any{{"deviceId": 0, "ts": 0, "v": 60.0}}) runtime.GC() var ms runtime.MemStats @@ -123,18 +123,18 @@ func TestStressCEP_SustainedLoad_HeapStable(t *testing.T) { runtime.ReadMemStats(&ms) heapEnd := ms.HeapAlloc - t.Logf("持续负载: %d 事件 / %v = %.0f ops/sec", events, dur, float64(events)/dur.Seconds()) - t.Logf("堆: %.2fMB → %.2fMB (delta %.2fMB)", + t.Logf("Continuous load: %d events / %v = %.0f ops/sec", events, dur, float64(events)/dur.Seconds()) + t.Logf("Dui: %.2fMB → %.2fMB (delta %.2fMB)", float64(heapStart)/1e6, float64(heapEnd)/1e6, float64(int64(heapEnd)-int64(heapStart))/1e6) require.Less(t, float64(int64(heapEnd)-int64(heapStart)), 100.0*1e6, "堆增量过大,疑为状态留存型泄漏:delta=%.2fMB", float64(int64(heapEnd)-int64(heapStart))/1e6) } -// --- 3. 分区 LRU 驱逐不泄漏 --- +// --- 3. Partitioned LRU Expulsion Without Leakage --- -// 5 万个不同分区(远超默认上限 maxPartitions=10000),每分区一条。LRU 驱逐在持续负载下不应 -// 泄漏、不应 panic。驻留若随总分区数线性增长即为驱逐失效。 +// 50,000 different partitions (far exceeding the default limit maxPartitions=10,000), one per partition. LRU destroyers should not be used under continuous load +// Leaks should not be panicked. If the residency increases linearly with the total number of partitions, it is considered a deportation failure. func TestStressCEP_PartitionEviction_NoLeak(t *testing.T) { s := streamsql.New(streamsql.WithBufferSizes(4096, 1024, 256)) require.NoError(t, s.Execute(cepStressSQL)) @@ -156,10 +156,10 @@ func TestStressCEP_PartitionEviction_NoLeak(t *testing.T) { runtime.ReadMemStats(&ms) heapEnd := ms.HeapAlloc - t.Logf("淘汰负载: %d 分区 / %v = %.0f ops/sec", distinct, dur, float64(distinct)/dur.Seconds()) - t.Logf("堆: %.2fMB → %.2fMB (delta %.2fMB)", + t.Logf("Elimination load: %d partition / %v = %.0f ops/sec", distinct, dur, float64(distinct)/dur.Seconds()) + t.Logf("Dui: %.2fMB → %.2fMB (delta %.2fMB)", float64(heapStart)/1e6, float64(heapEnd)/1e6, float64(int64(heapEnd)-int64(heapStart))/1e6) - // 驱逐应把旧分区回收,驻留仅近 LRU 上限个;驻留若随总分区数线性增长即为驱逐失效。 + // Expulsion should reclaim old partitions, and only reside near the LRU limit; If the residency increases linearly with the total number of partitions, it is considered a deportation failure. require.Less(t, float64(int64(heapEnd)-int64(heapStart)), 150.0*1e6, "堆随分区数线性增长,疑为 LRU 驱逐未回收:delta=%.2fMB", float64(int64(heapEnd)-int64(heapStart))/1e6) diff --git a/test/e2e/cep_test.go b/test/e2e/cep_test.go index f5dbd70..73f3afb 100644 --- a/test/e2e/cep_test.go +++ b/test/e2e/cep_test.go @@ -10,8 +10,8 @@ import ( "github.com/stretchr/testify/require" ) -// collectCEP 执行一条 MATCH_RECOGNIZE 查询,按序投入事件,等待 wantMatches 次匹配到达 sink。 -// 返回收到的每次匹配(每次 sink 回调的一批行)。 +// collectCEP executes a MATCH_RECOGNIZE query, inserts events in order, and waits for the wantMatches match to reach the sink. +// Returns each received match (a batch of lines for each sink callback). func collectCEP(t *testing.T, sql string, rows []map[string]any, wantMatches int) [][]map[string]any { t.Helper() s := streamsql.New() @@ -20,8 +20,8 @@ func collectCEP(t *testing.T, sql string, rows []map[string]any, wantMatches int var mu sync.Mutex var got [][]map[string]any done := make(chan struct{}, 256) - // 用 AddSyncSink:sync sink 在数据处理器 goroutine 内 inline 顺序执行,保证匹配到达顺序 - // 与产出顺序一致(AddSink 走异步 worker pool,多 worker 不保序)。 + // Use AddSyncSink: Sync sink executes inline sequentially within the data processor goroutine to ensure matching of the order of arrival + // Consistent with the output order (AddSink uses an asynchronous worker pool, multiple workers do not maintain order). s.AddSyncSink(func(r []map[string]any) { mu.Lock() got = append(got, r) @@ -35,7 +35,7 @@ func collectCEP(t *testing.T, sql string, rows []map[string]any, wantMatches int for _, r := range rows { s.Emit(r) } - // 等待期望匹配数(带超时)。CEP 匹配在触发事件处理期间产生(无需 Stop flush)。 + // Waiting for the expected match number (with timeout). CEP matches are generated during triggered event handling (no need to stop flush). for i := 0; i < wantMatches; i++ { select { case <-done: @@ -44,7 +44,7 @@ func collectCEP(t *testing.T, sql string, rows []map[string]any, wantMatches int t.Fatalf("timeout waiting for match %d/%d; got %d", i+1, wantMatches, len(got)) } } - // 再给一点时间让多余匹配(若有)落地,然后 Stop 收尾。 + // Give a little more time for any extra matches (if any) to land, then Stop to wrap up. time.Sleep(50 * time.Millisecond) s.Stop() @@ -53,7 +53,7 @@ func collectCEP(t *testing.T, sql string, rows []map[string]any, wantMatches int return got } -// flatten 把多次匹配的行展平为一个切片(用于 ONE ROW PER MATCH 断言)。 +// flatten flattens the rows of multiple matches into a slice (used for ONE ROW PER MATCH Assertions). func flatten(matches [][]map[string]any) []map[string]any { var out []map[string]any for _, m := range matches { @@ -62,7 +62,7 @@ func flatten(matches [][]map[string]any) []map[string]any { return out } -// 场景1:A{3} 连续越限确认。 +// Scenario 1: A{3} Consecutive limit exceedance confirmations. func TestCEP_ConsecutiveThreshold(t *testing.T) { sql := `SELECT * FROM stream MATCH_RECOGNIZE ( @@ -87,7 +87,7 @@ func TestCEP_ConsecutiveThreshold(t *testing.T) { assert.Equal(t, 80.0, asFloat64(flat[0]["peak"])) } -// 场景2:A B 过热后回落(故障前兆)。 +// Scenario 2: A and B overheat and then fall back (a warning sign). func TestCEP_RiseThenDrop(t *testing.T) { sql := `SELECT * FROM stream MATCH_RECOGNIZE ( @@ -108,7 +108,7 @@ func TestCEP_RiseThenDrop(t *testing.T) { assert.Equal(t, 90.0, asFloat64(flat[0]["drop"])) } -// 场景3:A B+ C 单调上升后转降(PREV 导航 + 聚合 MEASURES)。 +// Scenario 3: A B+ C monotonically rising and then falling (PREV navigation + aggregation MEASURES). func TestCEP_TrendReversal(t *testing.T) { sql := `SELECT * FROM stream MATCH_RECOGNIZE ( @@ -132,7 +132,7 @@ func TestCEP_TrendReversal(t *testing.T) { assert.Equal(t, 25.0, asFloat64(flat[0]["end"]), "last") } -// 场景4:A{5,} 振动突发(5+,以中断事件收尾)。 +// Scenario 4: A{5,} Vibration Burst (5+, ending with an interrupt event). func TestCEP_VibrationBurst(t *testing.T) { sql := `SELECT * FROM stream MATCH_RECOGNIZE ( @@ -158,7 +158,7 @@ func TestCEP_VibrationBurst(t *testing.T) { assert.Equal(t, 6.0, asFloat64(flat[0]["n"]), "6 consecutive vib") } -// 场景5:Start Process+ End 跨事件类型序列(工作流)。 +// Scenario 5: Start Process+ End cross-event type sequence (workflow). func TestCEP_CrossEventSequence(t *testing.T) { sql := `SELECT * FROM stream MATCH_RECOGNIZE ( @@ -180,7 +180,7 @@ func TestCEP_CrossEventSequence(t *testing.T) { assert.Equal(t, 4.0, asFloat64(flat[0]["steps"])) } -// PARTITION BY 按设备各自匹配。 +// PARTITION BY matches each device individually. func TestCEP_PartitionBy(t *testing.T) { sql := `SELECT * FROM stream MATCH_RECOGNIZE ( @@ -202,7 +202,7 @@ func TestCEP_PartitionBy(t *testing.T) { assert.Len(t, got, 2, "每设备各一次匹配") } -// 交替 A | B + CLASSIFIER。 +// Alternate A | B + CLASSIFIER. func TestCEP_Alternation(t *testing.T) { sql := `SELECT * FROM stream MATCH_RECOGNIZE ( @@ -224,7 +224,7 @@ func TestCEP_Alternation(t *testing.T) { assert.Equal(t, "B", flat[1]["c"]) } -// ALL ROWS PER MATCH 逐行输出 + RUNNING 聚合。 +// ALL ROWS PER MATCH output line by line + RUNNING aggregation. func TestCEP_AllRowsPerMatch(t *testing.T) { sql := `SELECT * FROM stream MATCH_RECOGNIZE ( @@ -252,7 +252,7 @@ func TestCEP_AllRowsPerMatch(t *testing.T) { } } -// AFTER MATCH SKIP TO NEXT ROW 允许重叠。 +// AFTER MATCH SKIP TO NEXT ROW allows overlap. func TestCEP_SkipToNextRow(t *testing.T) { sql := `SELECT * FROM stream MATCH_RECOGNIZE ( @@ -264,7 +264,7 @@ func TestCEP_SkipToNextRow(t *testing.T) { WITHIN '1h' DEFINE A AS v > 50 )` - // 4 连续:SKIP TO NEXT ROW → 匹配 (1,2),(2,3),(3,4) 共 3 次。 + // 4 Consecutive: SKIP TO NEXT ROW → Match (1,2), (2,3), (3,4) 3 times. rows := []map[string]any{ {"ts": 1, "v": 60}, {"ts": 2, "v": 70}, @@ -275,7 +275,7 @@ func TestCEP_SkipToNextRow(t *testing.T) { assert.Len(t, got, 3) } -// 分组模式 (A B)+:重复的 A-B 序列。 +// Grouping mode (A B)+: Repeating A-B sequences. func TestCEP_GroupRepetition(t *testing.T) { sql := `SELECT * FROM stream MATCH_RECOGNIZE ( @@ -291,7 +291,7 @@ func TestCEP_GroupRepetition(t *testing.T) { {"ts": 2, "k": 2}, {"ts": 3, "k": 1}, {"ts": 4, "k": 2}, - {"ts": 5, "k": 3}, // 中断收尾 + {"ts": 5, "k": 3}, // Interrupt and wrap up } got := collectCEP(t, sql, rows, 1) flat := flatten(got) @@ -299,7 +299,7 @@ func TestCEP_GroupRepetition(t *testing.T) { assert.Equal(t, 4.0, asFloat64(flat[0]["n"]), "两轮 A-B 共 4 行") } -// Execute 对非法 CEP(缺 PATTERN、排除模式)应 fail-fast。 +// Execute should fail-fast for illegal CEPs (missing PATTERN, excluding patterns). func TestCEP_ExecuteRejects(t *testing.T) { cases := []struct { name string @@ -319,7 +319,7 @@ func TestCEP_ExecuteRejects(t *testing.T) { } } -// EmitSync 对 CEP 查询应返回错误。 +// EmitSync should return an error for CEP queries. func TestCEP_EmitSyncRejected(t *testing.T) { s := streamsql.New() require.NoError(t, s.Execute(`SELECT * FROM stream MATCH_RECOGNIZE (ORDER BY ts PATTERN (A) DEFINE A AS v>0)`)) @@ -328,9 +328,9 @@ func TestCEP_EmitSyncRejected(t *testing.T) { assert.Error(t, err) } -// === 场景驱动:从真实 IoT 场景推导,含函数组合 === +// === Scenario-driven: derived from real IoT scenarios, including function combinations === -// 场景:温度连续上升 3 步,输出上升幅度(PREV 链 + MEASURES 算术 + 符号限定字段)。 +// Scenario: Temperature rises continuously by 3 steps, output increase (PREV chain + MEASURES arithmetic + symbol-limited fields). func TestCEP_Scenario_RiseStepsWithDelta(t *testing.T) { sql := `SELECT * FROM stream MATCH_RECOGNIZE ( ORDER BY ts @@ -351,7 +351,7 @@ func TestCEP_Scenario_RiseStepsWithDelta(t *testing.T) { assert.Equal(t, 20.0, asFloat64(flat[0]["rise"]), "30-10=20") } -// 场景:MEASURES 用 CASE 对匹配做分级(CASE + 聚合占位符)。 +// Scenario: MEASURES Uses CASE to rank matches (CASE + aggregated placeholder). func TestCEP_Scenario_CaseLevel(t *testing.T) { sql := `SELECT * FROM stream MATCH_RECOGNIZE ( ORDER BY ts @@ -373,7 +373,7 @@ func TestCEP_Scenario_CaseLevel(t *testing.T) { assert.Equal(t, "warn", flat[0]["level"], "MAX=120 → warn") } -// 场景:DEFINE 复合条件 + 函数(AND + 绝对值)。 +// Scenario: DEFINE Compound Condition + Function (AND + Absolute Value). func TestCEP_Scenario_DefineWithFunction(t *testing.T) { sql := `SELECT * FROM stream MATCH_RECOGNIZE ( ORDER BY ts @@ -384,9 +384,9 @@ func TestCEP_Scenario_DefineWithFunction(t *testing.T) { DEFINE A AS abs(v) > 50 AND type == "spike" )` rows := []map[string]any{ - {"ts": 1, "v": 10, "type": "spike"}, // abs(10)<50 → 不匹配 - {"ts": 2, "v": 80, "type": "spike"}, // 匹配 - {"ts": 3, "v": 80, "type": "normal"}, // type 不符 → 不匹配 + {"ts": 1, "v": 10, "type": "spike"}, // abs(10)<50 → mismatch + {"ts": 2, "v": 80, "type": "spike"}, // Match + {"ts": 3, "v": 80, "type": "normal"}, // Type mismatches → mismatches } got := collectCEP(t, sql, rows, 1) flat := flatten(got) @@ -394,7 +394,7 @@ func TestCEP_Scenario_DefineWithFunction(t *testing.T) { assert.Equal(t, 80.0, asFloat64(flat[0]["v"])) } -// 场景:输出含分区键(rulego 据此路由到对应设备)。 +// Scenario: output with partition keys (rulego routes to the corresponding device). func TestCEP_Scenario_PartitionKeyInOutput(t *testing.T) { sql := `SELECT * FROM stream MATCH_RECOGNIZE ( PARTITION BY dev @@ -421,7 +421,7 @@ func TestCEP_Scenario_PartitionKeyInOutput(t *testing.T) { assert.True(t, devs["d1"] && devs["d2"], "两设备各一匹配,输出含 dev: %v", devs) } -// 场景:重试后成功(A+ B:连续失败后一次成功)。 +// Scenario: Successful after retry (A+ B: One success after consecutive failures). func TestCEP_Scenario_RetryThenSuccess(t *testing.T) { sql := `SELECT * FROM stream MATCH_RECOGNIZE ( ORDER BY ts @@ -443,7 +443,7 @@ func TestCEP_Scenario_RetryThenSuccess(t *testing.T) { assert.Equal(t, 4.0, asFloat64(flat[0]["n"]), "3 fail + 1 ok") } -// 场景:MEASURES 算术范围(MAX-MIN、AVG)。 +// Scenario: MEASURES arithmetic range (MAX-MIN, AVG). func TestCEP_Scenario_ArithmeticMeasures(t *testing.T) { sql := `SELECT * FROM stream MATCH_RECOGNIZE ( ORDER BY ts @@ -465,7 +465,7 @@ func TestCEP_Scenario_ArithmeticMeasures(t *testing.T) { assert.Equal(t, 30.0, asFloat64(flat[0]["avg"]), "(10+50+30)/3=30") } -// 场景:可选中间步(Start (Process)? End:Process 0 或 1 次)。 +// Scenario: Optional intermediate step (Start (Process)? End: Process 0 or 1 time). func TestCEP_Scenario_OptionalMiddle(t *testing.T) { sql := `SELECT * FROM stream MATCH_RECOGNIZE ( ORDER BY ts @@ -480,14 +480,14 @@ func TestCEP_Scenario_OptionalMiddle(t *testing.T) { {"ts": 2, "s": "P"}, {"ts": 3, "s": "E"}, {"ts": 4, "s": "S"}, - {"ts": 5, "s": "E"}, // 无 Process 也匹配 + {"ts": 5, "s": "E"}, // No Process also matches } got := collectCEP(t, sql, rows, 2) assert.Len(t, got, 2, "S-P-E 与 S-E 各一次") } -// MEASURES 支持任意标量函数(非导航/聚合):upper/round/算术组合。 -// 顶层 SELECT 在 CEP 不生效,函数须写在 MEASURES 里。 +// MEASURES supports arbitrary scalar functions (non-navigation/aggregation): upper/round/arithmetic combinations. +// Top-level SELECT does not work in CEP; the function must be written in MEASURES. func TestCEP_MeasuresScalarFunctions(t *testing.T) { sql := `SELECT * FROM stream MATCH_RECOGNIZE ( ORDER BY ts @@ -506,7 +506,7 @@ func TestCEP_MeasuresScalarFunctions(t *testing.T) { assert.Equal(t, 4.4, asFloat64(flat[0]["vp1"]), "3.4+1") } -// ALL ROWS PER MATCH 下 FIRST/LAST 按 RUNNING 推进(与 COUNT 一致),而非恒为末行。 +// Under ALL ROWS PER MATCH, FIRST/LAST advance by RUNNING (consistent with COUNT), rather than always being the last row. func TestCEP_AllRows_FirstLastRunning(t *testing.T) { sql := `SELECT * FROM stream MATCH_RECOGNIZE ( ORDER BY ts @@ -520,7 +520,7 @@ func TestCEP_AllRows_FirstLastRunning(t *testing.T) { got := collectCEP(t, sql, rows, 1) require.Len(t, got, 1, "一次匹配") require.Len(t, got[0], 3, "ALL ROWS 输出 3 行") - // LAST RUNNING:10,20,30;FIRST 恒 10;COUNT RUNNING:1,2,3 + // LAST RUNNING: 10, 20, 30; FIRST constant 10; COUNT RUNNING: 1, 2, 3 assert.Equal(t, 10.0, asFloat64(got[0][0]["lv"])) assert.Equal(t, 20.0, asFloat64(got[0][1]["lv"])) assert.Equal(t, 30.0, asFloat64(got[0][2]["lv"])) @@ -528,7 +528,7 @@ func TestCEP_AllRows_FirstLastRunning(t *testing.T) { assert.Equal(t, 1.0, asFloat64(got[0][0]["n"])) } -// 外层 SELECT 投影 MEASURES 列:ONE ROW 下 SELECT 具体别名只输出选中的列。 +// Outer SELECT Projection MEASURES column: Under ONE ROW, the specific SELECT aliases only output the selected columns. func TestCEP_SelectProjectsMeasures(t *testing.T) { sql := `SELECT mn, peak FROM stream MATCH_RECOGNIZE ( ORDER BY ts @@ -548,7 +548,7 @@ func TestCEP_SelectProjectsMeasures(t *testing.T) { assert.False(t, hasTS, "ONE ROW SELECT 具体列不应漏入未选的输入字段") } -// 外层 SELECT 表达式对 MEASURES 列求值。 +// The outer SELECT expression evaluates the MEASURES column. func TestCEP_SelectExpressionOverMeasures(t *testing.T) { sql := `SELECT hi - lo AS span, hi FROM stream MATCH_RECOGNIZE ( ORDER BY ts @@ -566,7 +566,7 @@ func TestCEP_SelectExpressionOverMeasures(t *testing.T) { assert.Equal(t, 40.0, asFloat64(flat[0]["span"]), "hi-lo=50-10=40") } -// ONE ROW PER MATCH + SELECT *:关系只暴露 MEASURES 列(输入字段不漏入),对齐 Flink。 +// ONE ROW PER MATCH + SELECT *: The relationship exposes only the MEASURES column (no missing input fields), aligning with Flink. func TestCEP_SelectStarOneRowMeasuresOnly(t *testing.T) { sql := `SELECT * FROM stream MATCH_RECOGNIZE ( ORDER BY ts @@ -585,7 +585,7 @@ func TestCEP_SelectStarOneRowMeasuresOnly(t *testing.T) { assert.False(t, hasTS, "ONE ROW PER MATCH 仅暴露 MEASURES 列,不含输入字段") } -// ALL ROWS PER MATCH 暴露输入列:外层 SELECT 可引用输入字段(如 ts)。 +// ALL ROWS PER MATCH Exposed input column: Outer SELECT can reference input fields (such as ts). func TestCEP_AllRowsSelectInputField(t *testing.T) { sql := `SELECT ts, c FROM stream MATCH_RECOGNIZE ( ORDER BY ts @@ -605,7 +605,7 @@ func TestCEP_AllRowsSelectInputField(t *testing.T) { assert.Equal(t, 2.0, asFloat64(got[0][1]["ts"]), "次行 ts=2") } -// ALL ROWS PER MATCH + SELECT *:含输入字段与 MEASURES 列。 +// ALL ROWS PER MATCH + SELECT *: Includes input fields and MEASURES columns. func TestCEP_AllRowsSelectStarIncludesInput(t *testing.T) { sql := `SELECT * FROM stream MATCH_RECOGNIZE ( ORDER BY ts @@ -624,9 +624,9 @@ func TestCEP_AllRowsSelectStarIncludesInput(t *testing.T) { assert.True(t, hasV && hasC, "ALL ROWS SELECT * 应含输入列 v 与 MEASURES 列 c") } -// === 缺口补全:此前仅单元/探针覆盖、无 e2e 的场景 === +// === Gap Filling: Previously, only cell/probe coverage was used, with no e2e === -// PERMUTE(A, B):两种顺序都匹配(A,B) 与 (B,A)。 +// PERMUTE(A, B): Both sequences match (A, B) and (B, A). func TestCEP_Permute(t *testing.T) { sql := `SELECT * FROM stream MATCH_RECOGNIZE ( ORDER BY ts @@ -644,7 +644,7 @@ func TestCEP_Permute(t *testing.T) { assert.Equal(t, "A", flat[1]["last"], "[B,A]→末符号 A") } -// WITHIN 过期恢复:超窗的 A-B 作废,窗内的新 A-B 仍匹配。 +// WITHIN expired recovery: The A-B of the super window is voided, while the new A-B inside the window still matches. func TestCEP_WithinExpiryRecovery(t *testing.T) { const base = int64(1700000000000) sql := `SELECT * FROM stream MATCH_RECOGNIZE ( @@ -657,9 +657,9 @@ func TestCEP_WithinExpiryRecovery(t *testing.T) { )` rows := []map[string]any{ {"ts": base, "k": 1}, // A - {"ts": base + 70000, "k": 2}, // 距 A 70s > 1min → 过期 - {"ts": base + 100000, "k": 1}, // 新 A - {"ts": base + 100030, "k": 2}, // 距新 A 30ms < 1min → 匹配 + {"ts": base + 70000, "k": 2}, // Expired → 1 minute > from A 70s + {"ts": base + 100000, "k": 1}, // New A + {"ts": base + 100030, "k": 2}, // Matches → 1 minute < 30ms from the new A } got := collectCEP(t, sql, rows, 1) flat := flatten(got) @@ -667,7 +667,7 @@ func TestCEP_WithinExpiryRecovery(t *testing.T) { assert.Equal(t, 1.0, asFloat64(flat[0]["mn"]), "仅第二对在窗内匹配") } -// NEXT 导航:匹配末行 NEXT 越界为 nil。 +// NEXT Navigation: Matches the last line NEXT outbound as nil. func TestCEP_NextNavigation(t *testing.T) { sql := `SELECT * FROM stream MATCH_RECOGNIZE ( ORDER BY ts @@ -685,7 +685,7 @@ func TestCEP_NextNavigation(t *testing.T) { assert.Nil(t, flat[0]["nxt"], "末行 NEXT 越界应为 nil") } -// DEFINE 复合:OR 逻辑 + 引用另一符号字段(B AS v > A.v)。 +// DEFINE Complex: OR logic + reference another symbolic field (B AS v > A.v). func TestCEP_DefineOrAndCrossSymbol(t *testing.T) { sql := `SELECT * FROM stream MATCH_RECOGNIZE ( ORDER BY ts @@ -696,17 +696,17 @@ func TestCEP_DefineOrAndCrossSymbol(t *testing.T) { DEFINE A AS v > 10, B AS v > A.v OR k == 9 )` rows := []map[string]any{ - {"ts": 1, "v": 20, "k": 0}, // A: v>10 ✓;后续 B 不满足 → 失败 - {"ts": 2, "v": 5, "k": 0}, // B: 5>A.v(20)? 否;k==9? 否 → 不匹配 + {"ts": 1, "v": 20, "k": 0}, // A: v>10 ✓; Subsequently, B does not satisfy → failure + {"ts": 2, "v": 5, "k": 0}, // B: 5>A.v(20)? No; k==9? No → mismatch {"ts": 3, "v": 20, "k": 0}, // A - {"ts": 4, "v": 25, "k": 0}, // B: 25>20 ✓ → 匹配 + {"ts": 4, "v": 25, "k": 0}, // B: 25>20 ✓ → match } got := collectCEP(t, sql, rows, 1) flat := flatten(got) require.Len(t, flat, 1, "仅 A(3)B(4) 匹配") } -// 多字段 PARTITION BY:按 dev+tenant 联合隔离。 +// Multi-field PARTITION BY: Joint isolation by dev+tenant. func TestCEP_MultiPartitionBy(t *testing.T) { sql := `SELECT * FROM stream MATCH_RECOGNIZE ( PARTITION BY dev, tenant @@ -727,7 +727,7 @@ func TestCEP_MultiPartitionBy(t *testing.T) { assert.Len(t, got, 2, "dev+tenant 各一组各一次匹配") } -// SUM 聚合 MEASURES(此前只测了 AVG/MIN/MAX/COUNT)。 +// SUM Aggregation MEASURES (previously only AVG/MIN/MAX/COUNT were measured). func TestCEP_MeasuresSum(t *testing.T) { sql := `SELECT * FROM stream MATCH_RECOGNIZE ( ORDER BY ts @@ -745,7 +745,7 @@ func TestCEP_MeasuresSum(t *testing.T) { assert.Equal(t, 3.0, asFloat64(flat[0]["n"])) } -// A* 星号量词:0+ 个 A 后接 B。 +// A* Asterisk: 0+ A's followed by B. func TestCEP_StarQuantifier(t *testing.T) { sql := `SELECT * FROM stream MATCH_RECOGNIZE ( ORDER BY ts @@ -762,7 +762,7 @@ func TestCEP_StarQuantifier(t *testing.T) { assert.Equal(t, 3.0, asFloat64(flat[0]["n"]), "A* B:2A+1B=3") } -// AFTER MATCH SKIP TO LAST <符号>:解析+运行时(首行场景,断言匹配数)。 +// AFTER MATCH SKIP TO LAST : parsing and runtime (first-line case; assert the match count). func TestCEP_SkipToLastSymbol(t *testing.T) { sql := `SELECT * FROM stream MATCH_RECOGNIZE ( ORDER BY ts @@ -783,8 +783,8 @@ func TestCEP_SkipToLastSymbol(t *testing.T) { assert.Equal(t, 1.0, asFloat64(flat[0]["mn"])) } -// Stop 冲刷未闭合的贪婪匹配(A+ 无收尾事件,仅 Stop Flush 产出)。 -// 同时验证 Flush 输出也经 SELECT 投影(与 Process 一致)。 +// Stop flushes unclosed greedy matches (A+ has no finishing events, only Stop Flush outputs). +// At the same time, verify that the Flush output is also projected by SELECT (consistent with the Process). func TestCEP_FlushUnclosed(t *testing.T) { sql := `SELECT * FROM stream MATCH_RECOGNIZE ( ORDER BY ts @@ -806,15 +806,15 @@ func TestCEP_FlushUnclosed(t *testing.T) { for _, r := range []map[string]any{{"ts": 1, "k": 1}, {"ts": 2, "k": 1}, {"ts": 3, "k": 1}} { s.Emit(r) } - time.Sleep(100 * time.Millisecond) // 等处理器消费 - s.Stop() // Flush 产出未闭合的 A+ 突发 + time.Sleep(100 * time.Millisecond) // and other processor consumption + s.Stop() // Flush produces an unclosed A+ burst mu.Lock() defer mu.Unlock() require.Len(t, got, 1, "Flush 应输出未闭合的 A+ 突发") assert.Equal(t, 3.0, asFloat64(got[0]["n"]), "3 个 A") } -// 符号限定聚合 SUM(A.v) 仅对 A 标签行求和;SUM(v) 对全部行。 +// Symbol-limited aggregation SUM(A.v) only sums rows of A labels; SUM(v) for all rows. func TestCEP_SymbolScopedAggregate(t *testing.T) { sql := `SELECT * FROM stream MATCH_RECOGNIZE ( ORDER BY ts @@ -828,7 +828,7 @@ func TestCEP_SymbolScopedAggregate(t *testing.T) { {"ts": 1, "k": 1, "v": 1}, // A {"ts": 2, "k": 2, "v": 10}, // B {"ts": 3, "k": 2, "v": 100}, // B - {"ts": 4, "k": 3, "v": 0}, // 非 A/B:收尾 B+ + {"ts": 4, "k": 3, "v": 0}, // Non-A/B: Finishing B+ } got := collectCEP(t, sql, rows, 1) flat := flatten(got) @@ -837,7 +837,7 @@ func TestCEP_SymbolScopedAggregate(t *testing.T) { assert.Equal(t, 111.0, asFloat64(flat[0]["allv"]), "SUM(v) 全部行 = 1+10+100") } -// MATCH_RECOGNIZE ORDER BY DESC 流式下无意义(按到达序),Execute 期 fail-fast。 +// MATCH_RECOGNIZE ORDER BY DESC is meaningless in the flow (by order of arrival), Execute period fail-fast. func TestCEP_RejectsOrderByDesc(t *testing.T) { s := streamsql.New() err := s.Execute(`SELECT * FROM stream MATCH_RECOGNIZE (ORDER BY ts DESC PATTERN (A) DEFINE A AS v>0)`) @@ -845,8 +845,8 @@ func TestCEP_RejectsOrderByDesc(t *testing.T) { s.Stop() } -// SUBSET 在 MEASURES 引用:SUM(S.v) 对 S={A,B} 全部成分行求和;S.v 取成分末行。 -// 语法入口:ONE ROW PER MATCH + 外层 SELECT 具体别名(验证不漏未选列)。 +// SUBSET in MEASURES reference: SUM(S.v) sums all components of S={A,B}; S.v takes the last row of the components. +// Syntax entry: ONE ROW PER MATCH + outer SELECT specific alias (verifying no missed columns). func TestCEP_SubsetAggregate(t *testing.T) { sql := `SELECT sv, last, mn FROM stream MATCH_RECOGNIZE ( ORDER BY ts @@ -861,7 +861,7 @@ func TestCEP_SubsetAggregate(t *testing.T) { {"ts": 1, "k": 1, "v": 1}, {"ts": 2, "k": 2, "v": 10}, {"ts": 3, "k": 2, "v": 100}, - {"ts": 4, "k": 3, "v": 0}, // 非 A/B:收尾 B+ + {"ts": 4, "k": 3, "v": 0}, // Non-A/B: Finishing B+ } got := collectCEP(t, sql, rows, 1) flat := flatten(got) @@ -873,8 +873,8 @@ func TestCEP_SubsetAggregate(t *testing.T) { assert.False(t, hasAV, "SELECT 未选 av 不应漏入") } -// SUBSET 在 PATTERN 里作原子:PATTERN(S C)(S={A,B})→ (A|B) C,CLASSIFIER 返回真实成分。 -// 语法入口:ALL ROWS PER MATCH + 外层 SELECT 输入字段 ts 与 MEASURES 列 c。 +// SUBSET is an atom in a PATTERN: PATTERN(S C)(S={A,B})→ (A| B) C, CLASSIFIER returns the true composition. +// Syntax entry: ALL ROWS PER MATCH + outer SELECT input fields ts and MEASURES column c. func TestCEP_SubsetInPattern(t *testing.T) { sql := `SELECT ts, c FROM stream MATCH_RECOGNIZE ( ORDER BY ts @@ -886,7 +886,7 @@ func TestCEP_SubsetInPattern(t *testing.T) { DEFINE A AS k == 1, B AS k == 2, C AS k == 3 )` rows := []map[string]any{ - {"ts": 1, "k": 1}, // A(经 S 展开匹配) + {"ts": 1, "k": 1}, // A (Expand S for Matching) {"ts": 2, "k": 3}, // C } got := collectCEP(t, sql, rows, 1) @@ -898,7 +898,7 @@ func TestCEP_SubsetInPattern(t *testing.T) { assert.Equal(t, 2.0, asFloat64(got[0][1]["ts"])) } -// ALL ROWS PER MATCH 下 FINAL 聚合取整段匹配(恒定),RUNNING 截到当前行(累计)。 +// Under ALL ROWS PER MATCH, FINAL AGGREGATES THE entire segment match (constant), RUNNING cuts to the current row (cumulative). func TestCEP_FinalVsRunning(t *testing.T) { sql := `SELECT * FROM stream MATCH_RECOGNIZE ( ORDER BY ts @@ -912,7 +912,7 @@ func TestCEP_FinalVsRunning(t *testing.T) { got := collectCEP(t, sql, rows, 1) require.Len(t, got, 1, "一次匹配") require.Len(t, got[0], 3, "ALL ROWS 输出 3 行") - // FINAL SUM 恒为 60(整段);RUNNING SUM 累计 10/30/60。 + // FINAL SUM is always 60 (entire segment); RUNNING SUM cumulative 10/30/60. for _, r := range got[0] { assert.Equal(t, 60.0, asFloat64(r["fs"]), "FINAL SUM 恒 60") } @@ -921,7 +921,7 @@ func TestCEP_FinalVsRunning(t *testing.T) { assert.Equal(t, 60.0, asFloat64(got[0][2]["rs"]), "RUNNING 行2=60") } -// ONE ROW PER MATCH 下 FINAL 与默认(RUNNING)结果一致:cur 已是末行,二者相等。 +// Under ONE ROW PER MATCH, FINAL matches the default (RUNNING) result: cur is already the last row, and both are equal. func TestCEP_FinalOneRowNoChange(t *testing.T) { sql := `SELECT * FROM stream MATCH_RECOGNIZE ( ORDER BY ts @@ -939,8 +939,8 @@ func TestCEP_FinalOneRowNoChange(t *testing.T) { assert.Equal(t, 60.0, asFloat64(flat[0]["rs"]), "ONE ROW 下 FINAL 与默认一致") } -// WITHIN 主动过期:sweeper 定期清除超窗的部分匹配。空闲超过 WITHIN 后 A 被主动清除, -// 后续 B 无法续上 → 无匹配。需用近期 epoch 时间戳(sweeper 按 wall-clock 判过期)。 +// WITHIN Active Expire: The sweeper periodically clears partial matches of the overwindow. After the idle exceeds WITHIN and A is actively removed, +// Subsequent B cannot be renewed → no match. A recent epoch timestamp is required (sweeper presses wall-clock to indicate expire). func TestCEP_WithinSweeperExpires(t *testing.T) { sql := `SELECT * FROM stream MATCH_RECOGNIZE ( ORDER BY ts @@ -960,8 +960,8 @@ func TestCEP_WithinSweeperExpires(t *testing.T) { mu.Unlock() }) s.Emit(map[string]any{"ts": time.Now().UnixMilli(), "k": 1}) // A - time.Sleep(500 * time.Millisecond) // >WITHIN 且 >sweepInterval(100ms):sweeper 清除 A - s.Emit(map[string]any{"ts": time.Now().UnixMilli(), "k": 2}) // B 来时无 A → 无匹配 + time.Sleep(500 * time.Millisecond) // >WITHIN and >sweepInterval(100ms): sweeper clears A + s.Emit(map[string]any{"ts": time.Now().UnixMilli(), "k": 2}) // B comes without A→ No matching time.Sleep(200 * time.Millisecond) s.Stop() mu.Lock() @@ -969,7 +969,7 @@ func TestCEP_WithinSweeperExpires(t *testing.T) { assert.Empty(t, got, "sweeper 主动过期部分匹配,B 来时无 A → 无匹配") } -// 对照:sweeper 启用时不误删窗内匹配(AB 在 WITHIN 内连续到达 → 有匹配)。 +// Comparison: When sweeper is enabled, it does not delete in-window matches (AB arrives consecutively within WITHIN → matches are made). func TestCEP_WithinSweeperKeepsRecent(t *testing.T) { sql := `SELECT * FROM stream MATCH_RECOGNIZE ( ORDER BY ts @@ -990,8 +990,8 @@ func TestCEP_WithinSweeperKeepsRecent(t *testing.T) { assert.Equal(t, 1.0, asFloat64(flat[0]["mn"])) } -// 贪婪 A*(A/B DEFINE 重叠 v>0)选最长:3 事件 → 1 个匹配 [A,A,B](n=3)。 -// 重叠下贪婪延伸到流末 Flush 才选最长,故用 Stop 后断言(非 collectCEP)。 +// Greedy A*(A/B DEFINE overlaps v>0) Choose the longest: 3 events → 1 match [A,A,B](n=3). +// The longest is chosen when greed extends to the end of the flush, so a stop post-assertion is used (not collectCEP). func TestCEP_GreedyStarLongest(t *testing.T) { sql := `SELECT * FROM stream MATCH_RECOGNIZE ( ORDER BY ts @@ -1014,14 +1014,14 @@ func TestCEP_GreedyStarLongest(t *testing.T) { s.Emit(r) } time.Sleep(100 * time.Millisecond) - s.Stop() // 贪婪重叠:匹配在 Flush 选最长后产出 + s.Stop() // Greed Overlap: Matches after Flush picks the longest output mu.Lock() defer mu.Unlock() require.Len(t, got, 1, "贪婪 A* 选最长:1 个匹配 [A,A,B]") assert.Equal(t, 3.0, asFloat64(got[0]["n"]), "贪婪匹配 3 行 A,A,B") } -// 懒惰 A*?(A/B DEFINE 重叠)选最短:每位置 0 个 A + B → [B]×3(各 n=1)。 +// Lazy A*? (A/B DEFINE Overlap) Select the shortest: 0 A + B → [B]×3 (each n = 1). func TestCEP_ReluctantStarShortest(t *testing.T) { sql := `SELECT * FROM stream MATCH_RECOGNIZE ( ORDER BY ts @@ -1040,8 +1040,8 @@ func TestCEP_ReluctantStarShortest(t *testing.T) { } } -// 贪婪 pending 不被 WITHIN sweeper 删除:sweep 用 wall-clock、withinOk 用事件时间, -// 已完成的合法匹配(pending)只由 emitGreedy/Flush 产出。修复前 sweep 会误删 pending 致丢失。 +// Greedy pending is not deleted by WITHIN sweeper: sweep uses wall-clock, withinOk uses event time, +// Completed valid pendings are only generated by emitGreedy/Flush. Before repairing, sweep would accidentally delete pending and cause loss. func TestCEP_GreedyPendingNotSwept(t *testing.T) { sql := `SELECT * FROM stream MATCH_RECOGNIZE ( ORDER BY ts @@ -1065,7 +1065,7 @@ func TestCEP_GreedyPendingNotSwept(t *testing.T) { s.Emit(map[string]any{"ts": now.Add(time.Duration(i) * 10 * time.Millisecond).UnixMilli(), "v": i}) time.Sleep(10 * time.Millisecond) } - time.Sleep(400 * time.Millisecond) // >sweepInterval(100ms):sweep 多次跑,但 pending 须保留 + time.Sleep(400 * time.Millisecond) // >sweepInterval(100ms): Sweep multiple times, but pending must be retained s.Stop() mu.Lock() defer mu.Unlock() diff --git a/test/e2e/condition_complex_test.go b/test/e2e/condition_complex_test.go index b957143..18ff0f4 100644 --- a/test/e2e/condition_complex_test.go +++ b/test/e2e/condition_complex_test.go @@ -22,7 +22,7 @@ import ( "github.com/rulego/streamsql" ) -// TestComplexConditions 测试复杂条件和组合条件的支持 +// TestComplexConditions Tests support for complex and combined conditions func TestComplexConditions(t *testing.T) { t.Parallel() tests := []struct { @@ -148,13 +148,13 @@ func TestComplexConditions(t *testing.T) { t.Fatalf("Execute() failed: %v", err) } - // 使用EmitSync进行同步测试 + // Synchronization testing was conducted using EmitSync result, err := ssql.EmitSync(tt.data) if err != nil { t.Fatalf("EmitSync() failed: %v", err) } - // 检查结果是否符合预期 + // Check whether the results meet expectations if tt.expectMatch { if result == nil { t.Errorf("Expected match but got nil result. %s", tt.description) @@ -165,13 +165,13 @@ func TestComplexConditions(t *testing.T) { } } - // 清理资源 + // Release resources ssql.Stop() }) } } -// TestComplexConditionsWithLike 测试LIKE条件与其他条件的组合 +// TestComplexConditionsWithLike tests the combination of LIKE conditions and other conditions func TestComplexConditionsWithLike(t *testing.T) { t.Parallel() tests := []struct { @@ -228,7 +228,7 @@ func TestComplexConditionsWithLike(t *testing.T) { } } -// TestComplexConditionsWithNullChecks 测试NULL检查与其他条件的组合 +// TestComplexConditionsWithNullChecks Tests NULL checks combined with other conditions func TestComplexConditionsWithNullChecks(t *testing.T) { t.Parallel() tests := []struct { @@ -242,7 +242,7 @@ func TestComplexConditionsWithNullChecks(t *testing.T) { sql: "SELECT * FROM stream WHERE description IS NULL AND temperature > 20", data: map[string]any{ "temperature": 25.0, - // description字段缺失,应该被视为null + // The description field is missing and should be considered null }, expectMatch: true, }, @@ -251,7 +251,7 @@ func TestComplexConditionsWithNullChecks(t *testing.T) { sql: "SELECT * FROM stream WHERE description IS NOT NULL OR temperature > 30", data: map[string]any{ "temperature": 35.0, - // description字段缺失,但temperature条件满足 + // The description field is missing, but the temperature condition is satisfied }, expectMatch: true, }, diff --git a/test/e2e/custom_functions_single_entry_test.go b/test/e2e/custom_functions_single_entry_test.go index d03003c..1fc9344 100644 --- a/test/e2e/custom_functions_single_entry_test.go +++ b/test/e2e/custom_functions_single_entry_test.go @@ -12,11 +12,11 @@ import ( "github.com/stretchr/testify/require" ) -// 本文件验证:标量 / 聚合 / 分析三类自定义函数,各自只需 functions.Register 一个入口, -// 即可端到端在 SQL 中生效——无需 aggregator.Register 或 RegisterAggregatorAdapter 二次注册。 -// 全局 registry 跨测试共享,函数名统一用 zz_ 前缀并 defer Unregister 清理,避免污染。 +// This document verifies: Scalar, Aggregation, and Analysis are three types of custom functions, each only requiring functions.Register a single entry point, +// This allows end-to-end implementation in the SQL—no need for a aggregator.Register or RegisterAggregatorAdapter to register again. +// Global registry is shared across tests, function names are uniformly prefixed with zz_ and defer unregister for cleanup to avoid contamination. -// ===== 标量:RegisterCustomFunction 单入口 ===== +// ===== Scalar: RegisterCustomFunction Single Entry ===== func TestSingleEntry_ScalarFunction(t *testing.T) { require.NoError(t, functions.RegisterCustomFunction( @@ -41,14 +41,14 @@ func TestSingleEntry_ScalarFunction(t *testing.T) { require.Len(t, r, 1) assert.Equal(t, 42.0, r[0]["d"]) case <-time.After(2 * time.Second): - t.Fatal("标量函数结果超时") + t.Fatal("Scalar function result timeout") } } -// ===== 聚合:实现 functions.AggregatorFunction + 只 functions.Register ===== +// ===== Aggregation: Implementing functions.AggregatorFunction + only functions.Register ===== -// zzMySum 完整实现 functions.AggregatorFunction,仅通过 functions.Register 注册, -// 不调 aggregator.Register。证明聚合只需一个入口(adapter 由 registry 自动接通)。 +// zzMySum fully implements functions.AggregatorFunction, only through functions.Register, +// Not tuned aggregator.Register. Proof aggregation requires only one entry point (the adapter is automatically enabled by the registry). type zzMySum struct { *functions.BaseFunction sum float64 @@ -59,7 +59,7 @@ func newZzMySum() *zzMySum { return &zzMySum{BaseFunction: functions.NewBaseFunction("zz_my_sum", functions.TypeAggregation, "test", "custom sum", 1, -1)} } -func (f *zzMySum) Validate(args []any) error { return f.ValidateArgCount(args) } +func (f *zzMySum) Validate(args []any) error { return f.ValidateArgCount(args) } func (f *zzMySum) Execute(ctx *functions.FunctionContext, args []any) (any, error) { s := 0.0 for _, a := range args { @@ -85,7 +85,7 @@ func (f *zzMySum) Result() any { } return f.sum } -func (f *zzMySum) Reset() { f.sum = 0; f.ok = false } +func (f *zzMySum) Reset() { f.sum = 0; f.ok = false } func (f *zzMySum) Clone() functions.AggregatorFunction { return &zzMySum{BaseFunction: f.BaseFunction, sum: f.sum, ok: f.ok} } @@ -115,13 +115,13 @@ func TestSingleEntry_AggregateFunction(t *testing.T) { assert.Equal(t, "d1", r[0]["device"]) assert.Equal(t, 6.0, r[0]["s"]) case <-time.After(2 * time.Second): - t.Fatal("聚合函数结果超时") + t.Fatal("The aggregate function result times out") } } -// ===== 分析:实现 functions.StatefulAnalytic + 只 functions.Register ===== +// ===== Analysis: Implementing functions.StatefulAnalytic + only functions.Register ===== -// zzMyPrev 是 lag 语义的自定义分析函数,仅通过 functions.Register 注册。 +// zzMyPrev is a custom analysis function for lag semantics, and only through functions.Register. type zzMyPrev struct { *functions.BaseFunction } @@ -130,7 +130,7 @@ func newZzMyPrev() *zzMyPrev { return &zzMyPrev{BaseFunction: functions.NewBaseFunction("zz_my_prev", functions.TypeAnalytical, "test", "previous value", 1, 1)} } -func (f *zzMyPrev) Validate(args []any) error { return f.ValidateArgCount(args) } +func (f *zzMyPrev) Validate(args []any) error { return f.ValidateArgCount(args) } func (f *zzMyPrev) Execute(ctx *functions.FunctionContext, args []any) (any, error) { return nil, fmt.Errorf("analytic function %q must be used with OVER", f.GetName()) } diff --git a/test/e2e/custom_functions_test.go b/test/e2e/custom_functions_test.go index 2fbe6bf..6d39748 100644 --- a/test/e2e/custom_functions_test.go +++ b/test/e2e/custom_functions_test.go @@ -18,12 +18,12 @@ import ( "github.com/stretchr/testify/assert" ) -// 本文件测试串行执行(不加 t.Parallel):向全局 function registry 注册自定义函数, -// 跨测试/跨文件存在重名注册(square/distance/get_type 等),并行会导致 "already registered" 冲突。 +// This file tests serial execution (without t.Parallel): registering a custom function in the global function registry, +// There are duplicate registrations across tests/files (square/distance/get_type, etc.), and parallelism will cause "already registered" conflicts. -// TestCustomMathFunctions 测试自定义数学函数 +// TestCustomMathFunctions Test custom math functions func TestCustomMathFunctions(t *testing.T) { - // 注册平方函数 + // Register the square function err := functions.RegisterCustomFunction( "square", functions.TypeMath, @@ -38,7 +38,7 @@ func TestCustomMathFunctions(t *testing.T) { assert.NoError(t, err) defer functions.Unregister("square") - // 注册距离计算函数 + // Register the distance calculation function err = functions.RegisterCustomFunction( "distance", functions.TypeMath, @@ -58,7 +58,7 @@ func TestCustomMathFunctions(t *testing.T) { assert.NoError(t, err) defer functions.Unregister("distance") - // 测试在SQL中使用 + // Testing is used in SQL ssql := streamsql.New() defer ssql.Stop() @@ -74,30 +74,30 @@ func TestCustomMathFunctions(t *testing.T) { err = ssql.Execute(sql) assert.NoError(t, err) - // 创建结果接收通道 + // Create a result receiving channel resultChan := make(chan any, 10) ssql.AddSink(func(result []map[string]any) { resultChan <- result }) - // 添加测试数据 + // Add test data testData := map[string]any{ "device": "sensor1", "value": 5.0, "x1": 0.0, "y1": 0.0, "x2": 3.0, - "y2": 4.0, // 距离应该是5 + "y2": 4.0, // The distance should be 5 } ssql.Emit(testData) - // 等待窗口触发 + // Wait for the window to trigger time.Sleep(1 * time.Second) ssql.TriggerWindow() time.Sleep(500 * time.Millisecond) - // 验证结果 + // Verify the results select { case result := <-resultChan: resultSlice, ok := result.([]map[string]any) @@ -109,13 +109,13 @@ func TestCustomMathFunctions(t *testing.T) { assert.Equal(t, 25.0, item["squared_value"]) // 5^2 = 25 assert.Equal(t, 5.0, item["calculated_distance"]) // sqrt((3-0)^2 + (4-0)^2) = 5 case <-time.After(2 * time.Second): - t.Fatal("测试超时") + t.Fatal("Test timeout") } } -// TestCustomStringFunctions 测试自定义字符串函数 +// TestCustomStringFunctions Tests custom string functions func TestCustomStringFunctions(t *testing.T) { - // 注册字符串反转函数 + // Register the string invert function err := functions.RegisterCustomFunction( "reverse_str", functions.TypeString, @@ -136,7 +136,7 @@ func TestCustomStringFunctions(t *testing.T) { assert.NoError(t, err) defer functions.Unregister("reverse_str") - // 注册JSON提取函数 + // Register the JSON extraction function err = functions.RegisterCustomFunction( "json_get", functions.TypeString, @@ -164,7 +164,7 @@ func TestCustomStringFunctions(t *testing.T) { assert.NoError(t, err) defer functions.Unregister("json_get") - // 测试在SQL中使用 + // Testing is used in SQL ssql := streamsql.New() defer ssql.Stop() @@ -179,13 +179,13 @@ func TestCustomStringFunctions(t *testing.T) { err = ssql.Execute(sql) assert.NoError(t, err) - // 创建结果接收通道 + // Create a result receiving channel resultChan := make(chan any, 10) ssql.AddSink(func(result []map[string]any) { resultChan <- result }) - // 添加测试数据 + // Add test data testData := map[string]any{ "device": "sensor1", "metadata": `{"version":"1.0","type":"temperature"}`, @@ -194,7 +194,7 @@ func TestCustomStringFunctions(t *testing.T) { ssql.Emit(testData) time.Sleep(200 * time.Millisecond) - // 验证结果 + // Verify the results select { case result := <-resultChan: resultSlice, ok := result.([]map[string]any) @@ -203,16 +203,16 @@ func TestCustomStringFunctions(t *testing.T) { item := resultSlice[0] assert.Equal(t, "sensor1", item["device"]) - assert.Equal(t, "1rosnes", item["reversed_device"]) // "sensor1" 反转 + assert.Equal(t, "1rosnes", item["reversed_device"]) // "sensor1" reverses assert.Equal(t, "1.0", item["version"]) case <-time.After(2 * time.Second): - t.Fatal("测试超时") + t.Fatal("Test timeout") } } -// TestCustomConversionFunctions 测试自定义转换函数 +// TestCustomConversionFunctions Test the custom conversion function func TestCustomConversionFunctions(t *testing.T) { - // 注册IP地址转换函数 + // Register the IP address translation function err := functions.RegisterCustomFunction( "ip_to_num", functions.TypeConversion, @@ -238,7 +238,7 @@ func TestCustomConversionFunctions(t *testing.T) { assert.NoError(t, err) defer functions.Unregister("ip_to_num") - // 注册字节大小格式化函数 + // Register byte size formatting function err = functions.RegisterCustomFunction( "format_bytes", functions.TypeConversion, @@ -261,10 +261,10 @@ func TestCustomConversionFunctions(t *testing.T) { assert.NoError(t, err) defer functions.Unregister("format_bytes") - // 测试函数直接调用 + // The test function is called directly ctx := &functions.FunctionContext{Data: make(map[string]any)} - // 测试IP转换 + // Testing IP conversion ipFunc, exists := functions.Get("ip_to_num") assert.True(t, exists) @@ -273,7 +273,7 @@ func TestCustomConversionFunctions(t *testing.T) { expectedIP := int64(192)<<24 + int64(168)<<16 + int64(1)<<8 + int64(100) assert.Equal(t, expectedIP, result) - // 测试字节格式化 + // Test byte formatting bytesFunc, exists := functions.Get("format_bytes") assert.True(t, exists) @@ -282,23 +282,23 @@ func TestCustomConversionFunctions(t *testing.T) { assert.Equal(t, "1.00 GB", result) } -// TestCustomAggregateFunctions 测试自定义聚合函数 +// TestCustomAggregateFunctions Test custom aggregate functions func TestCustomAggregateFunctions(t *testing.T) { - // 注册几何平均数聚合函数 + // Register the geometric mean aggregation function functions.Register(NewGeometricMeanFunction()) aggregator.Register("geometric_mean", func() aggregator.AggregatorFunction { return &GeometricMeanAggregator{} }) defer functions.Unregister("geometric_mean") - // 注册众数聚合函数 + // Register the mode aggregation function functions.Register(NewModeFunction()) aggregator.Register("mode_value", func() aggregator.AggregatorFunction { return &ModeAggregator{} }) defer functions.Unregister("mode_value") - // 测试在SQL中使用 + // Testing is used in SQL ssql := streamsql.New() defer ssql.Stop() @@ -314,13 +314,13 @@ func TestCustomAggregateFunctions(t *testing.T) { err := ssql.Execute(sql) assert.NoError(t, err) - // 创建结果接收通道 + // Create a result receiving channel resultChan := make(chan any, 10) ssql.AddSink(func(result []map[string]any) { resultChan <- result }) - // 添加测试数据 + // Add test data testData := []map[string]any{ {"device": "sensor1", "value": 2.0, "category": "A"}, {"device": "sensor1", "value": 8.0, "category": "A"}, @@ -336,7 +336,7 @@ func TestCustomAggregateFunctions(t *testing.T) { ssql.TriggerWindow() time.Sleep(500 * time.Millisecond) - // 验证结果 + // Verify the results select { case result := <-resultChan: resultSlice, ok := result.([]map[string]any) @@ -346,21 +346,21 @@ func TestCustomAggregateFunctions(t *testing.T) { item := resultSlice[0] assert.Equal(t, "sensor1", item["device"]) - // 几何平均数: (2 * 8 * 32 * 128) ^ (1/4) = 16 + // Geometric mean: (2 * 8 * 32 * 128) ^ (1/4) = 16 geoMean, ok := item["geo_mean"].(float64) assert.True(t, ok) assert.InEpsilon(t, 16.0, geoMean, 0.01) - // 众数: A出现3次,B出现1次,所以众数是A + // Mode: A appears 3 times, B appears 1 time, so the mode is A mode := item["most_common"] assert.Equal(t, "A", mode) case <-time.After(3 * time.Second): - t.Fatal("测试超时") + t.Fatal("Test timeout") } } -// GeometricMeanFunction 几何平均数函数 +// GeometricMeanFunction Geometric mean function type GeometricMeanFunction struct { *functions.BaseFunction } @@ -382,10 +382,10 @@ func (f *GeometricMeanFunction) Validate(args []any) error { } func (f *GeometricMeanFunction) Execute(ctx *functions.FunctionContext, args []any) (any, error) { - return nil, nil // 实际逻辑在聚合器中 + return nil, nil // The actual logic is in the aggregator } -// GeometricMeanAggregator 几何平均数聚合器 +// GeometricMeanAggregator: Geometric mean aggregator type GeometricMeanAggregator struct { values []float64 } @@ -413,7 +413,7 @@ func (g *GeometricMeanAggregator) Result() any { return math.Pow(product, 1.0/float64(len(g.values))) } -// ModeFunction 众数函数 +// ModeFunction mode function type ModeFunction struct { *functions.BaseFunction } @@ -435,10 +435,10 @@ func (f *ModeFunction) Validate(args []any) error { } func (f *ModeFunction) Execute(ctx *functions.FunctionContext, args []any) (any, error) { - return nil, nil // 实际逻辑在聚合器中 + return nil, nil // The actual logic is in the aggregator } -// ModeAggregator 众数聚合器 +// ModeAggregator type ModeAggregator struct { counts map[string]int } @@ -470,9 +470,9 @@ func (m *ModeAggregator) Result() any { return mode } -// TestFunctionManagement 测试函数管理功能 +// TestFunctionManagement is a test function management feature func TestFunctionManagement(t *testing.T) { - // 注册测试函数 + // Register the test function err := functions.RegisterCustomFunction( "test_func", functions.TypeCustom, @@ -485,17 +485,17 @@ func TestFunctionManagement(t *testing.T) { ) assert.NoError(t, err) - // 测试函数查找 + // Test function search fn, exists := functions.Get("test_func") assert.True(t, exists) assert.Equal(t, "test_func", fn.GetName()) assert.Equal(t, functions.TypeCustom, fn.GetType()) - // 测试函数列表 + // Test the function list allFunctions := functions.ListAll() assert.Contains(t, allFunctions, "test_func") - // 测试按类型获取 + // Tests are obtained by type customFunctions := functions.GetByType(functions.TypeCustom) found := false for _, f := range customFunctions { @@ -506,18 +506,18 @@ func TestFunctionManagement(t *testing.T) { } assert.True(t, found) - // 测试函数注销 + // Test function logout success := functions.Unregister("test_func") assert.True(t, success) - // 验证函数已被注销 + // The validation function has been logged off _, exists = functions.Get("test_func") assert.False(t, exists) } -// TestCustomFunctionWithAggregation 测试自定义函数与聚合函数结合使用 +// TestCustomFunctionWithAggregation Test custom functions are used together with aggregation functions func TestCustomFunctionWithAggregation(t *testing.T) { - // 注册温度转换函数 + // Register the temperature conversion function err := functions.RegisterCustomFunction( "celsius_to_fahrenheit", functions.TypeConversion, @@ -533,7 +533,7 @@ func TestCustomFunctionWithAggregation(t *testing.T) { assert.NoError(t, err) defer functions.Unregister("celsius_to_fahrenheit") - // 测试在聚合SQL中使用 + // Testing is used in aggregated SQL ssql := streamsql.New() defer ssql.Stop() @@ -549,13 +549,13 @@ func TestCustomFunctionWithAggregation(t *testing.T) { err = ssql.Execute(sql) assert.NoError(t, err) - // 创建结果接收通道 + // Create a result receiving channel resultChan := make(chan any, 10) ssql.AddSink(func(result []map[string]any) { resultChan <- result }) - // 添加测试数据(摄氏度) + // Add test data (Celsius) testData := []map[string]any{ {"device": "thermometer", "temperature": 0.0}, // 32°F {"device": "thermometer", "temperature": 100.0}, // 212°F @@ -569,7 +569,7 @@ func TestCustomFunctionWithAggregation(t *testing.T) { ssql.TriggerWindow() time.Sleep(500 * time.Millisecond) - // 验证结果 + // Verify the results select { case result := <-resultChan: resultSlice, ok := result.([]map[string]any) @@ -579,24 +579,24 @@ func TestCustomFunctionWithAggregation(t *testing.T) { item := resultSlice[0] assert.Equal(t, "thermometer", item["device"]) - // 平均华氏度: (32 + 212) / 2 = 122 + // Average Fahrenheit: (32 + 212) / 2 = 122 avgFahrenheit, ok := item["avg_fahrenheit"].(float64) assert.True(t, ok) assert.InEpsilon(t, 122.0, avgFahrenheit, 0.01) - // 最大华氏度: 212 + // Maximum Fahrenheit: 212 maxFahrenheit, ok := item["max_fahrenheit"].(float64) assert.True(t, ok) assert.InEpsilon(t, 212.0, maxFahrenheit, 0.01) case <-time.After(3 * time.Second): - t.Fatal("测试超时") + t.Fatal("Test timeout") } } -// TestDebugCustomFunctions 调试自定义函数问题 +// TestDebugCustomFunctions Debugging custom function issues func TestDebugCustomFunctions(t *testing.T) { - // 注册简单的平方函数 + // Register simple square functions err := functions.RegisterCustomFunction( "square", functions.TypeMath, @@ -612,27 +612,27 @@ func TestDebugCustomFunctions(t *testing.T) { assert.NoError(t, err) defer functions.Unregister("square") - // 测试函数是否能被找到 + // Test whether the function can be found fn, exists := functions.Get("square") assert.True(t, exists) fmt.Printf("Function found: %s, type: %s\n", fn.GetName(), fn.GetType()) - // 测试表达式解析 + // Test expression analysis expr, err := expr.NewExpression("square(value)") assert.NoError(t, err) - // 获取表达式字段 + // Get the expression field fields := expr.GetFields() fmt.Printf("Expression fields: %v\n", fields) - // 测试表达式计算 + // Test expression calculation data := map[string]any{"value": 5.0} result, err := expr.Evaluate(data) assert.NoError(t, err) fmt.Printf("Expression result: %v\n", result) assert.Equal(t, 25.0, result) - // 测试SQL解析 + // Test SQL parsing parser := rsql.NewParser("SELECT square(value) as squared FROM stream") stmt, err := parser.Parse() assert.NoError(t, err) @@ -641,9 +641,9 @@ func TestDebugCustomFunctions(t *testing.T) { assert.NoError(t, err) } -// TestDebugMultiParameterFunction 测试多参数自定义函数 +// TestDebugMultiParameterFunction is a custom multi-parameter test func TestDebugMultiParameterFunction(t *testing.T) { - // 注册距离计算函数 + // Register the distance calculation function err := functions.RegisterCustomFunction( "distance", functions.TypeMath, @@ -664,11 +664,11 @@ func TestDebugMultiParameterFunction(t *testing.T) { assert.NoError(t, err) defer functions.Unregister("distance") - // 测试表达式解析 + // Test expression analysis expr, err := expr.NewExpression("distance(x1, y1, x2, y2)") assert.NoError(t, err) - // 测试表达式计算 + // Test expression calculation data := map[string]any{ "x1": 0.0, "y1": 0.0, @@ -679,7 +679,7 @@ func TestDebugMultiParameterFunction(t *testing.T) { assert.NoError(t, err) assert.Equal(t, 5.0, result) - // 测试SQL解析 + // Test SQL parsing parser := rsql.NewParser("SELECT AVG(distance(x1, y1, x2, y2)) as avg_distance FROM stream GROUP BY device, TumblingWindow('1s')") stmt, err := parser.Parse() assert.NoError(t, err) diff --git a/test/e2e/doc_cases_test.go b/test/e2e/doc_cases_test.go index e7b43a3..3b32426 100644 --- a/test/e2e/doc_cases_test.go +++ b/test/e2e/doc_cases_test.go @@ -8,11 +8,11 @@ import ( "github.com/stretchr/testify/require" ) -// 本文件镜像 rulego-doc「案例集锦」里的 SQL,防止文档示例与代码漂移: -// 一旦 streamsql 改动让某条案例 SQL 失效(解析失败/输出变了),这里会先红。 -// 案例文档:docs/03.StreamSQL/31.案例集锦/ +// This document mirrors the SQL from the rulego-doc "Case Collection" to prevent document examples and code from drifting: +// If a streamsql change causes a case SQL to become invalid (parsing failed/output changes), it will be red first. +// Case Document: docs/03.StreamSQL/31. Case Collection/ -// 05.数据过滤与转换:非聚合 EmitSync,过滤 + 算术换算 + CASE 分级。 +// 05. Data filtering and conversion: Non-aggregation EmitSync, filtering + arithmetic conversion + CASE hierarchy. func TestDocCases_FilterTransform(t *testing.T) { ssql := streamsql.New() const sql = `SELECT deviceId, @@ -28,15 +28,15 @@ WHERE temperature > 0 AND temperature < 100` cases := []struct { in map[string]any - filter bool // 期望被 WHERE 过滤(EmitSync 返回 nil) + filter bool // Expected to be filtered by WHERE (EmitSync returns nil) level string tempF float64 }{ {map[string]any{"deviceId": "dev-01", "temperature": 28.0}, false, "OK", 82.4}, {map[string]any{"deviceId": "dev-02", "temperature": 32.0}, false, "WARNING", 89.6}, {map[string]any{"deviceId": "dev-03", "temperature": 38.0}, false, "CRITICAL", 100.4}, - {map[string]any{"deviceId": "dev-04", "temperature": 999.0}, true, "", 0}, // 越界过滤 - {map[string]any{"deviceId": "dev-05", "temperature": nil}, true, "", 0}, // nil 过滤 + {map[string]any{"deviceId": "dev-04", "temperature": 999.0}, true, "", 0}, // Filtering beyond boundaries + {map[string]any{"deviceId": "dev-05", "temperature": nil}, true, "", 0}, // NIL filtration } for _, c := range cases { out, err := ssql.EmitSync(c.in) @@ -51,13 +51,13 @@ WHERE temperature > 0 AND temperature < 100` } } -// 01.流表JOIN元数据增强:Execute 后 RegisterTable,INNER JOIN 补属性,无匹配丢弃。 +// 01. Enhanced JOIN metadata in flow tables: After executing, RegisterTable and INNER JOIN are supplemented with attributes, and no match is discarded. func TestDocCases_JoinEnrichment(t *testing.T) { ssql := streamsql.New() const sql = `SELECT deviceId, m.location, m.model, temperature FROM stream JOIN meta m ON deviceId = m.deviceId` require.NoError(t, ssql.Execute(sql)) - // RegisterTable 必须在 Execute 之后。 + // RegisterTable must be placed after Execute. _, err := ssql.RegisterTable("meta", []map[string]any{ {"deviceId": "d1", "location": "plantA", "model": "TX-100"}, {"deviceId": "d2", "location": "plantB", "model": "TX-200"}, @@ -75,12 +75,12 @@ FROM stream JOIN meta m ON deviceId = m.deviceId` require.NotNil(t, r2) assert.Equal(t, "plantB", r2["location"]) - // d9 无匹配 → INNER JOIN 丢弃(nil,非 error)。 + // d9 No match → INNER JOIN discard (nil, not error). r9, _ := ssql.EmitSync(map[string]any{"deviceId": "d9", "temperature": 40.0}) assert.Nil(t, r9, "INNER JOIN 无匹配应丢弃") } -// 03.变更数据捕获 场景1:全局 lag(无分区),电流从 ≤300 跳到 >300 的时刻。 +// 03. Change Data Capture Scenario 1: Global lag (no partitioning), the moment when current jumps from ≤300 to >300. func TestDocCases_CDC_GlobalLag(t *testing.T) { ssql := streamsql.New() require.NoError(t, ssql.Execute(`SELECT current, ts FROM stream WHERE current > 300 AND lag(current) <= 300`)) @@ -108,7 +108,7 @@ func TestDocCases_CDC_GlobalLag(t *testing.T) { assert.Equal(t, 400, outs[2]["current"]) } -// 03.变更数据捕获 场景3:OVER WHEN 限定 lag 范围,只盯 deviceId=1。 +// 03. Change Data Capture Scenario 3: OVER WHEN Limit the lag range, focusing only on deviceId=1. func TestDocCases_CDC_WhenLimitedLag(t *testing.T) { ssql := streamsql.New() require.NoError(t, ssql.Execute(`SELECT current, deviceId, ts FROM stream WHERE current > 300 AND deviceId = 1 AND lag(current) OVER (WHEN deviceId = 1) < 300`)) @@ -135,7 +135,7 @@ func TestDocCases_CDC_WhenLimitedLag(t *testing.T) { assert.Equal(t, 1, outs[0]["deviceId"]) } -// 时间类窗口 SQL 至少保证可解析执行(输出行为受时间调度影响,单独在窗口测试里覆盖)。 +// Time-based window SQL must at least ensure parsable execution (output behavior is affected by time scheduling and is overlaid separately in window testing). func TestDocCases_WindowSQL_Parses(t *testing.T) { t.Parallel() sqls := map[string]string{ diff --git a/test/e2e/error_handling_test.go b/test/e2e/error_handling_test.go index 1d9e6d1..2b6059f 100644 --- a/test/e2e/error_handling_test.go +++ b/test/e2e/error_handling_test.go @@ -11,7 +11,7 @@ import ( "github.com/stretchr/testify/require" ) -// TestStreamSQLErrorHandling 测试StreamSQL的错误处理机制 +// TestStreamSQLErrorHandling tests StreamSQL's error handling mechanism func TestStreamSQLErrorHandling(t *testing.T) { t.Parallel() t.Run("invalid SQL syntax", func(t *testing.T) { @@ -26,7 +26,7 @@ func TestStreamSQLErrorHandling(t *testing.T) { ssql := streamsql.New() defer ssql.Stop() err := ssql.Execute("FROM stream WHERE id > 1") - // 修改后的解析器会对缺少SELECT关键字进行严格检查 + // The modified parser will strictly check for missing SELECT keywords require.NotNil(t, err) require.Contains(t, err.Error(), "Expected SELECT") }) @@ -71,14 +71,14 @@ func TestStreamSQLErrorHandling(t *testing.T) { t.Run("Emit without Execute", func(t *testing.T) { ssql := streamsql.New() defer ssql.Stop() - // 这不应该引发panic,但也不会有任何效果 + // This shouldn't trigger panic, but it won't have any effect either ssql.Emit(map[string]any{"id": 1}) }) t.Run("Stop without Execute", func(t *testing.T) { ssql := streamsql.New() defer ssql.Stop() - // 这不应该引发panic + // This should not trigger panic ssql.Stop() }) @@ -122,19 +122,19 @@ func TestStreamSQLErrorHandling(t *testing.T) { t.Run("AddSink without Execute", func(t *testing.T) { ssql := streamsql.New() defer ssql.Stop() - // 这不应该引发panic + // This should not trigger panic ssql.AddSink(func(results []map[string]any) {}) }) t.Run("PrintTable without Execute", func(t *testing.T) { ssql := streamsql.New() defer ssql.Stop() - // 这不应该引发panic + // This should not trigger panic ssql.PrintTable() }) } -// TestStreamSQLEdgeCases 测试边界条件和特殊情况 +// TestStreamSQLEdgeCases tests boundary conditions and special cases func TestStreamSQLEdgeCases(t *testing.T) { t.Parallel() t.Run("empty SQL string", func(t *testing.T) { @@ -155,8 +155,8 @@ func TestStreamSQLEdgeCases(t *testing.T) { ssql := streamsql.New() defer ssql.Stop() err := ssql.Execute("-- This is a comment\nSELECT id FROM stream") - // 根据实际的SQL解析器行为,这可能成功或失败 - // 这里我们只是确保不会panic + // Depending on the actual SQL parser behavior, this may succeed or fail + // Here, we just make sure there is no panic _ = err }) @@ -172,7 +172,7 @@ func TestStreamSQLEdgeCases(t *testing.T) { } longSQL += " FROM stream" err := ssql.Execute(longSQL) - // 应该能够处理长SQL语句 + // It should be able to handle long SQL statements _ = err }) @@ -182,7 +182,7 @@ func TestStreamSQLEdgeCases(t *testing.T) { err1 := ssql.Execute("SELECT id FROM stream") require.Nil(t, err1) - // 第二次Execute应该失败,因为已经执行过了 + // The second execute should fail because it has already been executed err2 := ssql.Execute("SELECT name FROM stream") require.Error(t, err2) require.Contains(t, err2.Error(), "Execute() has already been called") @@ -196,7 +196,7 @@ func TestStreamSQLEdgeCases(t *testing.T) { ssql.Stop() - // 停止后再次Execute应该失败,因为已经执行过了 + // After stopping, execute should fail again because it has already been executed err = ssql.Execute("SELECT name FROM stream") require.Error(t, err) require.Contains(t, err.Error(), "Execute() has already been called") @@ -230,20 +230,20 @@ func TestStreamSQLEdgeCases(t *testing.T) { done <- true }() - // 等待两个goroutine完成 + // Wait for both goroutines to complete <-done <-done - // 验证只有一个成功,一个失败 + // Validation is only one success, one failure assert.Equal(t, int32(1), atomic.LoadInt32(&successCount)) assert.Equal(t, int32(1), atomic.LoadInt32(&errorCount)) - // 确保最终有一个有效的stream + // Make sure you end up with an effective stream require.NotNil(t, ssql.Stream()) }) } -// TestStreamSQLNilHandling 测试nil值处理 +// TestStreamSQLNilHandling tests nil value processing func TestStreamSQLNilHandling(t *testing.T) { t.Parallel() t.Run("emit nil map", func(t *testing.T) { @@ -252,7 +252,7 @@ func TestStreamSQLNilHandling(t *testing.T) { err := ssql.Execute("SELECT id FROM stream") require.Nil(t, err) - // 发送nil数据不应该panic + // Sending nil data should not be panicked ssql.Emit(nil) ssql.Stop() }) @@ -263,7 +263,7 @@ func TestStreamSQLNilHandling(t *testing.T) { err := ssql.Execute("SELECT id, name FROM stream") require.Nil(t, err) - // 发送包含nil值的数据 + // Send data containing the nil value ssql.Emit(map[string]any{ "id": 1, "name": nil, @@ -279,13 +279,13 @@ func TestStreamSQLNilHandling(t *testing.T) { // EmitSync with nil data _, err = ssql.EmitSync(nil) - // 根据实现,这可能返回错误或处理nil值 + // Depending on the implementation, this may return errors or handle nil values _ = err ssql.Stop() }) } -// TestStreamSQLComplexQueries 测试复杂查询 +// TestStreamSQLComplexQueries tests complex queries func TestStreamSQLComplexQueries(t *testing.T) { t.Parallel() t.Run("query with multiple fields", func(t *testing.T) { @@ -330,7 +330,7 @@ func TestStreamSQLComplexQueries(t *testing.T) { ssql := streamsql.New() defer ssql.Stop() err := ssql.Execute("SELECT id, value FROM stream GROUP BY TumblingWindow('5s')") - // 根据实际实现,这可能成功或失败 + // Depending on the actual implementation, this may succeed or fail _ = err if err == nil { ssql.Stop() @@ -338,7 +338,7 @@ func TestStreamSQLComplexQueries(t *testing.T) { }) } -// TestStreamSQLDataTypes 测试不同数据类型 +// TestStreamSQLDataTypes tests different data types func TestStreamSQLDataTypes(t *testing.T) { t.Parallel() t.Run("string data types", func(t *testing.T) { @@ -418,7 +418,7 @@ func TestStreamSQLDataTypes(t *testing.T) { }) } -// TestStreamSQLStressTest 压力测试 +// TestStreamSQLStressTest Stress testing func TestStreamSQLStressTest(t *testing.T) { t.Parallel() t.Run("high frequency emissions", func(t *testing.T) { @@ -427,7 +427,7 @@ func TestStreamSQLStressTest(t *testing.T) { err := ssql.Execute("SELECT id FROM stream") require.Nil(t, err) - // 高频率发送数据 + // Data is transmitted at high frequencies for i := 0; i < 1000; i++ { ssql.Emit(map[string]any{"id": i}) } @@ -440,7 +440,7 @@ func TestStreamSQLStressTest(t *testing.T) { err := ssql.Execute("SELECT data FROM stream") require.Nil(t, err) - // 发送大数据负载 + // Send large data loads largeString := make([]byte, 10*1024) // 10KB for i := range largeString { largeString[i] = byte('A' + (i % 26)) diff --git a/test/e2e/evaluator_semantics_test.go b/test/e2e/evaluator_semantics_test.go index 9789d7e..1871595 100644 --- a/test/e2e/evaluator_semantics_test.go +++ b/test/e2e/evaluator_semantics_test.go @@ -8,22 +8,22 @@ import ( "github.com/stretchr/testify/require" ) -// 求值器语义探针:从 SQL 标准语义推导期望值,验证 NULL 传播 / 字符串参与算术 / 路由边界。 -// 目的:探明三套求值器接缝(NULL 三值、float64 陷阱、substring 路由)是潜在风险还是现行 bug。 +// Evaluator semantic probe: Deduces expected values from SQL standard semantics to verify NULL propagation +// Objective: To determine whether the three sets of evaluator seams (NULL three-value, float64 trap, substring routing) are potential risks or existing bugs. -// NULL 在算术里应传播(SQL:NULL + n = NULL),不应被当作 0。 +// NULL should be propagated in arithmetic (SQL:NULL + n = NULL) and should not be treated as 0. func TestEvalSem_NullInArithmetic(t *testing.T) { ssql := streamsql.New() require.NoError(t, ssql.Execute("SELECT temperature + 10 AS x FROM stream")) defer ssql.Stop() - // 正常值:5 + 10 = 15 + // Normal value: 5 + 10 = 15 r1, err := ssql.EmitSync(map[string]any{"temperature": 5.0}) require.NoError(t, err) require.NotNil(t, r1) assert.Equal(t, 15.0, asFloat64(r1["x"])) - // nil 应传播为 nil,而非 0+10=10 + // NIL should propagate as nil, not 0+10=10 r2, err := ssql.EmitSync(map[string]any{"temperature": nil}) require.NoError(t, err) if r2 != nil { @@ -31,25 +31,25 @@ func TestEvalSem_NullInArithmetic(t *testing.T) { } } -// WHERE 中 NULL 与数值比较:SQL 三值逻辑下 NULL > 20 为 UNKNOWN,行应被过滤掉。 +// Comparison of NULL and Numeric Values in WHERE Values: In SQL three-value logic, NULL > 20 is UNKNOWN, so rows should be filtered out. func TestEvalSem_NullInWhereComparison(t *testing.T) { ssql := streamsql.New() require.NoError(t, ssql.Execute("SELECT temperature FROM stream WHERE temperature > 20")) defer ssql.Stop() - // 正常值通过 + // Normal values pass r1, err := ssql.EmitSync(map[string]any{"temperature": 25.0}) require.NoError(t, err) require.NotNil(t, r1, "25 > 20 应通过 WHERE") - // nil 比较应为 UNKNOWN → 行被过滤(r2=nil) + // nil comparison should be UNKNOWN → rows filtered (r2=nil) r2, err := ssql.EmitSync(map[string]any{"temperature": nil}) require.NoError(t, err) assert.Nil(t, r2, "NULL > 20 应为 UNKNOWN,行应被过滤") } -// 字符串参与算术:SQL 下字符串与数值运算应为类型错误/NULL,不应把字符串长度当数值。 -// 探测 expr 包 evaluateNode 的 float64 陷阱(非数字字符串 → float64(len))。 +// String Participation in Arithmetic: In SQL, string and numeric operations should be type error/NULL, and string length should not be treated as a value. +// Detect float64 traps (non-numeric strings → float64(len)) of the expr packet evaluateNode. func TestEvalSem_StringInArithmetic(t *testing.T) { ssql := streamsql.New() require.NoError(t, ssql.Execute("SELECT name * 2 AS x FROM stream")) @@ -57,13 +57,13 @@ func TestEvalSem_StringInArithmetic(t *testing.T) { r, err := ssql.EmitSync(map[string]any{"name": "abc"}) require.NoError(t, err) - // "abc" 长度为 3;若命中 float64 陷阱会得到 6.0。SQL 正确结果应是 NULL/错误,绝不是 6.0。 + // "abc" is 3 lengths; hitting the float64 trap grants 6.0. The correct SQL result should be NULL/error, definitely not 6.0. if r != nil { assert.NotEqual(t, 6.0, asFloat64(r["x"]), "字符串不应被当作长度 3 参与算术得 6.0(float64 陷阱)") } } -// CASE 中 NULL 分支:未命中且无 ELSE → NULL(SQL 标准)。 +// NULL branch in CASE: Missed and without ELSE → NULL (SQL standard). func TestEvalSem_CaseNoMatchNull(t *testing.T) { ssql := streamsql.New() require.NoError(t, ssql.Execute("SELECT CASE WHEN temperature > 100 THEN 'hot' END AS label FROM stream")) diff --git a/test/e2e/function_advanced_test.go b/test/e2e/function_advanced_test.go index 2db9afb..f077e5c 100644 --- a/test/e2e/function_advanced_test.go +++ b/test/e2e/function_advanced_test.go @@ -19,67 +19,67 @@ func TestExpressionInAggregation(t *testing.T) { ssql := streamsql.New() defer ssql.Stop() - // 测试在聚合函数中使用表达式 + // Test the use of expressions in the aggregate function var rsql = "SELECT device, AVG(temperature * 1.8 + 32) as fahrenheit FROM stream GROUP BY device, TumblingWindow('1s')" err := ssql.Execute(rsql) assert.Nil(t, err) strm := ssql - //fmt.Println("开始测试表达式功能") + //fmt.Println("Start testing the expression functionality") - // 添加测试数据,温度使用摄氏度 - // 不使用事件时间,不需要时间戳字段 + // Add test data and use temperature in degrees Celsius + // No event time is used, and no timestamp field is needed testData := []map[string]any{ - {"device": "aa", "temperature": 0.0}, // 华氏度应为 32 - {"device": "aa", "temperature": 100.0}, // 华氏度应为 212 - {"device": "bb", "temperature": 20.0}, // 华氏度应为 68 - {"device": "bb", "temperature": 30.0}, // 华氏度应为 86 + {"device": "aa", "temperature": 0.0}, // Fahrenheit should be 32 + {"device": "aa", "temperature": 100.0}, // Fahrenheit should be 212 + {"device": "bb", "temperature": 20.0}, // The degree of Fahrenheit should be 68 + {"device": "bb", "temperature": 30.0}, // Fahrenheit should be 86 } - // 添加数据 - //fmt.Println("添加测试数据") + // Add data + //fmt.Println("Add test data") for _, data := range testData { strm.Emit(data) } - // 创建结果接收通道 + // Create a result receiving channel resultChan := make(chan any, 10) - // 添加结果回调 + // Add result callbacks strm.AddSink(func(result []map[string]any) { - //fmt.Printf("接收到结果: %v\n", result) + //fmt.Printf("Result received: %v\n", result) resultChan <- result }) - // 等待窗口触发(处理时间模式) - //fmt.Println("等待窗口初始化...") + // Wait window trigger (processing time mode) + //fmt.Println("Waiting for window initialization...") time.Sleep(1 * time.Second) - // 手动触发窗口 - //fmt.Println("手动触发窗口") + // Manually trigger the window + //fmt.Println("Manually trigger the window") strm.TriggerWindow() - // 等待结果 + // Wait for the results ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) defer cancel() var actual any select { case actual = <-resultChan: - //fmt.Println("成功接收到结果") + //fmt.Println("Successfully received the results") cancel() case <-ctx.Done(): - t.Fatal("测试超时,未收到结果") + t.Fatal("The test timed out and no results were received") } - // 验证结果 + // Verify the results resultSlice, ok := actual.([]map[string]any) require.True(t, ok, "结果应该是[]map[string]any类型") - // 验证结果数量 + // Verification of the number of results assert.Len(t, resultSlice, 2, "应该有2个设备的聚合结果") - // 检查设备及其华氏度温度 + // Check the equipment and its Fahrenheit temperature for _, result := range resultSlice { device, _ := result["device"].(string) fahrenheit, ok := result["fahrenheit"].(float64) @@ -87,15 +87,15 @@ func TestExpressionInAggregation(t *testing.T) { assert.True(t, ok, "fahrenheit应该是float64类型") if device == "aa" { - // (0 + 100)/2 = 50 摄氏度,转华氏度为 50*1.8+32 = 122 + // (0 + 100)/2 = 50 degrees Celsius, so the conversion to Fahrenheit is 50*1.8+32 = 122 assert.InEpsilon(t, 122.0, fahrenheit, 0.001, "aa设备的平均华氏温度应为122") } else if device == "bb" { - // (20 + 30)/2 = 25 摄氏度,转华氏度为 25*1.8+32 = 77 + // (20 + 30)/2 = 25 degrees Celsius, so the transition is 25*1.8 + 32 = 77 assert.InEpsilon(t, 77.0, fahrenheit, 0.001, "bb设备的平均华氏温度应为77") } } - //fmt.Println("表达式测试完成") + //fmt.Println("Expression testing complete") } func TestAdvancedFunctionsInSQL(t *testing.T) { @@ -103,16 +103,16 @@ func TestAdvancedFunctionsInSQL(t *testing.T) { ssql := streamsql.New() defer ssql.Stop() - // 测试使用新函数系统的复杂SQL查询 + // Testing complex SQL queries using the new function system var rsql = "SELECT device, AVG(abs(temperature - 20)) as abs_diff, CONCAT(device, '_processed') as device_name FROM stream GROUP BY device, TumblingWindow('1s')" err := ssql.Execute(rsql) assert.Nil(t, err) strm := ssql - //fmt.Println("开始测试高级函数功能") + //fmt.Println("Start testing advanced function features") - // 添加测试数据 - // 不使用事件时间,不需要时间戳字段 + // Add test data + // No event time is used, and no timestamp field is needed testData := []map[string]any{ {"device": "sensor1", "temperature": 15.0}, // abs(15-20) = 5 {"device": "sensor1", "temperature": 25.0}, // abs(25-20) = 5 @@ -120,50 +120,50 @@ func TestAdvancedFunctionsInSQL(t *testing.T) { {"device": "sensor2", "temperature": 22.0}, // abs(22-20) = 2 } - // 添加数据 - //fmt.Println("添加测试数据") + // Add data + //fmt.Println("Add test data") for _, data := range testData { strm.Emit(data) } - // 创建结果接收通道 + // Create a result receiving channel resultChan := make(chan any, 10) - // 添加结果回调 + // Add result callbacks strm.AddSink(func(result []map[string]any) { - //fmt.Printf("接收到结果: %v\n", result) + //fmt.Printf("Result received: %v\n", result) resultChan <- result }) - // 等待窗口触发(处理时间模式) - //fmt.Println("等待窗口初始化...") + // Wait window trigger (processing time mode) + //fmt.Println("Waiting for window initialization...") time.Sleep(1 * time.Second) - // 手动触发窗口 - //fmt.Println("手动触发窗口") + // Manually trigger the window + //fmt.Println("Manually trigger the window") strm.TriggerWindow() - // 等待结果 + // Wait for the results ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) defer cancel() var actual any select { case actual = <-resultChan: - //fmt.Println("成功接收到结果") + //fmt.Println("Successfully received the results") cancel() case <-ctx.Done(): - t.Fatal("测试超时,未收到结果") + t.Fatal("The test timed out and no results were received") } - // 验证结果 + // Verify the results resultSlice, ok := actual.([]map[string]any) require.True(t, ok, "结果应该是[]map[string]any类型") - // 验证结果数量 + // Verification of the number of results assert.Len(t, resultSlice, 2, "应该有2个设备的聚合结果") - // 检查设备及其计算结果 + // Inspect the equipment and its calculation results for _, result := range resultSlice { device, _ := result["device"].(string) absDiff, ok := result["abs_diff"].(float64) @@ -183,12 +183,12 @@ func TestAdvancedFunctionsInSQL(t *testing.T) { } } - //fmt.Println("高级函数测试完成") + //fmt.Println("Advanced function testing completed") } func TestCustomFunctionInSQL(t *testing.T) { t.Parallel() - // 注册自定义函数:温度华氏度转摄氏度 + // Register a custom function: Temperature Fahrenheit to Celsius err := functions.RegisterCustomFunction("fahrenheit_to_celsius", functions.TypeCustom, "温度转换", "华氏度转摄氏度", 1, 1, func(ctx *functions.FunctionContext, args []any) (any, error) { fahrenheit := cast.ToFloat64(args[0]) @@ -201,16 +201,16 @@ func TestCustomFunctionInSQL(t *testing.T) { ssql := streamsql.New() defer ssql.Stop() - // 测试使用自定义函数的SQL查询 + // Testing SQL queries using custom functions var rsql = "SELECT device, AVG(fahrenheit_to_celsius(temperature)) as avg_celsius FROM stream GROUP BY device, TumblingWindow('1s')" err = ssql.Execute(rsql) assert.Nil(t, err) strm := ssql - //fmt.Println("开始测试自定义函数功能") + //fmt.Println("Start testing the custom function features") - // 添加测试数据(华氏度) - // 不使用事件时间,不需要时间戳字段 + // Add test data (Fahrenheit) + // No event time is used, and no timestamp field is needed testData := []map[string]any{ {"device": "thermometer1", "temperature": 32.0}, // 0°C {"device": "thermometer1", "temperature": 212.0}, // 100°C @@ -218,50 +218,50 @@ func TestCustomFunctionInSQL(t *testing.T) { {"device": "thermometer2", "temperature": 86.0}, // 30°C } - // 添加数据 - //fmt.Println("添加测试数据") + // Add data + //fmt.Println("Add test data") for _, data := range testData { strm.Emit(data) } - // 创建结果接收通道 + // Create a result receiving channel resultChan := make(chan any, 10) - // 添加结果回调 + // Add result callbacks strm.AddSink(func(result []map[string]any) { - //fmt.Printf("接收到结果: %v\n", result) + //fmt.Printf("Result received: %v\n", result) resultChan <- result }) - // 等待窗口触发(处理时间模式) - //fmt.Println("等待窗口初始化...") + // Wait window trigger (processing time mode) + //fmt.Println("Waiting for window initialization...") time.Sleep(1 * time.Second) - // 手动触发窗口 - //fmt.Println("手动触发窗口") + // Manually trigger the window + //fmt.Println("Manually trigger the window") strm.TriggerWindow() - // 等待结果 + // Wait for the results ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) defer cancel() var actual any select { case actual = <-resultChan: - //fmt.Println("成功接收到结果") + //fmt.Println("Successfully received the results") cancel() case <-ctx.Done(): - t.Fatal("测试超时,未收到结果") + t.Fatal("The test timed out and no results were received") } - // 验证结果 + // Verify the results resultSlice, ok := actual.([]map[string]any) require.True(t, ok, "结果应该是[]map[string]any类型") - // 验证结果数量 + // Verification of the number of results assert.Len(t, resultSlice, 2, "应该有2个设备的聚合结果") - // 检查设备及其计算结果 + // Inspect the equipment and its calculation results for _, result := range resultSlice { device, _ := result["device"].(string) avgCelsius, ok := result["avg_celsius"].(float64) @@ -277,7 +277,7 @@ func TestCustomFunctionInSQL(t *testing.T) { } } - //fmt.Println("自定义函数测试完成") + //fmt.Println("Custom function testing complete") } func TestNewAggregateFunctionsInSQL(t *testing.T) { @@ -285,16 +285,16 @@ func TestNewAggregateFunctionsInSQL(t *testing.T) { ssql := streamsql.New() defer ssql.Stop() - // 测试使用新聚合函数的SQL查询 + // Testing SQL queries using the new aggregation function var rsql = "SELECT device, collect(temperature) as temp_values, last_value(temperature) as last_temp, merge_agg(status) as all_status FROM stream GROUP BY device, TumblingWindow('1s')" err := ssql.Execute(rsql) assert.Nil(t, err) strm := ssql - //fmt.Println("开始测试新聚合函数功能") + //fmt.Println("Start testing the new aggregation function features") - // 添加测试数据 - // 不使用事件时间,不需要时间戳字段 + // Add test data + // No event time is used, and no timestamp field is needed testData := []map[string]any{ {"device": "sensor1", "temperature": 15.0, "status": "good"}, {"device": "sensor1", "temperature": 25.0, "status": "ok"}, @@ -302,50 +302,50 @@ func TestNewAggregateFunctionsInSQL(t *testing.T) { {"device": "sensor2", "temperature": 22.0, "status": "warning"}, } - // 添加数据 - //fmt.Println("添加测试数据") + // Add data + //fmt.Println("Add test data") for _, data := range testData { strm.Emit(data) } - // 创建结果接收通道 + // Create a result receiving channel resultChan := make(chan any, 10) - // 添加结果回调 + // Add result callbacks strm.AddSink(func(result []map[string]any) { - //fmt.Printf("接收到结果: %v\n", result) + //fmt.Printf("Result received: %v\n", result) resultChan <- result }) - // 等待窗口触发(处理时间模式) - //fmt.Println("等待窗口初始化...") + // Wait window trigger (processing time mode) + //fmt.Println("Waiting for window initialization...") time.Sleep(1 * time.Second) - // 手动触发窗口 - //fmt.Println("手动触发窗口") + // Manually trigger the window + //fmt.Println("Manually trigger the window") strm.TriggerWindow() - // 等待结果 + // Wait for the results ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) defer cancel() var actual any select { case actual = <-resultChan: - //fmt.Println("成功接收到结果") + //fmt.Println("Successfully received the results") cancel() case <-ctx.Done(): - t.Fatal("测试超时,未收到结果") + t.Fatal("The test timed out and no results were received") } - // 验证结果 + // Verify the results resultSlice, ok := actual.([]map[string]any) require.True(t, ok, "结果应该是[]map[string]any类型") - // 验证结果数量 + // Verification of the number of results assert.Len(t, resultSlice, 2, "应该有2个设备的聚合结果") - // 检查设备及其聚合结果 + // Inspect the equipment and its aggregated results for _, result := range resultSlice { device, _ := result["device"].(string) tempValues, ok1 := result["temp_values"] @@ -357,35 +357,35 @@ func TestNewAggregateFunctionsInSQL(t *testing.T) { assert.True(t, ok3, "all_status应该是string类型") if device == "sensor1" { - // collect函数应该收集[15.0, 25.0] + // The collect function should collect [15.0, 25.0] values, ok := tempValues.([]any) assert.True(t, ok, "temp_values应该是数组") assert.Len(t, values, 2, "sensor1应该有2个温度值") assert.Contains(t, values, 15.0) assert.Contains(t, values, 25.0) - // last_value应该是25.0 + // last_value should be 25.0 assert.Equal(t, 25.0, lastTemp) - // merge_agg应该是"good,ok" + // merge_agg should be "good,ok" assert.Equal(t, "good,ok", allStatus) } else if device == "sensor2" { - // collect函数应该收集[18.0, 22.0] + // The collect function should collect [18.0, 22.0] values, ok := tempValues.([]any) assert.True(t, ok, "temp_values应该是数组") assert.Len(t, values, 2, "sensor2应该有2个温度值") assert.Contains(t, values, 18.0) assert.Contains(t, values, 22.0) - // last_value应该是22.0 + // last_value should be 22.0 assert.Equal(t, 22.0, lastTemp) - // merge_agg应该是"good,warning" + // merge_agg should be "good,warning" assert.Equal(t, "good,warning", allStatus) } } - //fmt.Println("新聚合函数测试完成") + //fmt.Println("New aggregator function testing completed") } func TestStatisticalAggregateFunctionsInSQL(t *testing.T) { @@ -393,16 +393,16 @@ func TestStatisticalAggregateFunctionsInSQL(t *testing.T) { ssql := streamsql.New() defer ssql.Stop() - // 测试使用统计聚合函数的SQL查询 + // Testing SQL queries using statistical aggregation functions var rsql = "SELECT device, stddevs(temperature) as sample_stddev, var(temperature) as population_var, vars(temperature) as sample_var FROM stream GROUP BY device, TumblingWindow('1s')" err := ssql.Execute(rsql) assert.Nil(t, err) strm := ssql - //fmt.Println("开始测试统计聚合函数功能") + //fmt.Println("Start testing the statistical aggregation function feature") - // 添加测试数据 - // 不使用事件时间,不需要时间戳字段 + // Add test data + // No event time is used, and no timestamp field is needed testData := []map[string]any{ {"device": "sensor1", "temperature": 10.0}, {"device": "sensor1", "temperature": 20.0}, @@ -411,50 +411,50 @@ func TestStatisticalAggregateFunctionsInSQL(t *testing.T) { {"device": "sensor2", "temperature": 25.0}, } - // 添加数据 - //fmt.Println("添加测试数据") + // Add data + //fmt.Println("Add test data") for _, data := range testData { strm.Emit(data) } - // 创建结果接收通道 + // Create a result receiving channel resultChan := make(chan any, 10) - // 添加结果回调 + // Add result callbacks strm.AddSink(func(result []map[string]any) { - //fmt.Printf("接收到结果: %v\n", result) + //fmt.Printf("Result received: %v\n", result) resultChan <- result }) - // 等待窗口触发(处理时间模式) - //fmt.Println("等待窗口初始化...") + // Wait window trigger (processing time mode) + //fmt.Println("Waiting for window initialization...") time.Sleep(1 * time.Second) - // 手动触发窗口 - //fmt.Println("手动触发窗口") + // Manually trigger the window + //fmt.Println("Manually trigger the window") strm.TriggerWindow() - // 等待结果 + // Wait for the results ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) defer cancel() var actual any select { case actual = <-resultChan: - //fmt.Println("成功接收到结果") + //fmt.Println("Successfully received the results") cancel() case <-ctx.Done(): - t.Fatal("测试超时,未收到结果") + t.Fatal("The test timed out and no results were received") } - // 验证结果 + // Verify the results resultSlice, ok := actual.([]map[string]any) require.True(t, ok, "结果应该是[]map[string]any类型") - // 验证结果数量 + // Verification of the number of results assert.Len(t, resultSlice, 2, "应该有2个设备的聚合结果") - // 检查设备及其统计结果 + // Inspect the equipment and its statistical results for _, result := range resultSlice { device, _ := result["device"].(string) sampleStddev, ok1 := result["sample_stddev"].(float64) @@ -466,25 +466,25 @@ func TestStatisticalAggregateFunctionsInSQL(t *testing.T) { assert.True(t, ok3, "sample_var应该是float64类型") if device == "sensor1" { - // sensor1: [10, 20, 30], 平均值=20 - // 总体方差 = ((10-20)² + (20-20)² + (30-20)²) / 3 = (100 + 0 + 100) / 3 = 66.67 - // 样本方差 = 200 / 2 = 100 - // 样本标准差 = sqrt(100) = 10 + // sensor1: [10, 20, 30], mean = 20 + // Population variance = ((10-20)² + (20-20)² + (30-20)²) / 3 = (100 + 0 + 100) / 3 = 66.67 + // Sample variance = 200 / 2 = 100 + // Sample standard deviation = sqrt(100) = 10 assert.InEpsilon(t, 10.0, sampleStddev, 0.001, "sensor1的样本标准差应约为10") assert.InEpsilon(t, 66.67, populationVar, 0.1, "sensor1的总体方差应约为66.67") assert.InEpsilon(t, 100.0, sampleVar, 0.001, "sensor1的样本方差应约为100") } else if device == "sensor2" { - // sensor2: [15, 25], 平均值=20 - // 总体方差 = ((15-20)² + (25-20)²) / 2 = (25 + 25) / 2 = 25 - // 样本方差 = 50 / 1 = 50 - // 样本标准差 = sqrt(50) = 7.07 + // sensor2: [15, 25], mean value = 20 + // Population variance = ((15-20)² + (25-20)²) / 2 = (25 + 25) / 2 = 25 + // Sample variance = 50 / 1 = 50 + // Sample standard deviation = sqrt(50) = 7.07 assert.InEpsilon(t, 7.07, sampleStddev, 0.1, "sensor2的样本标准差应约为7.07") assert.InEpsilon(t, 25.0, populationVar, 0.001, "sensor2的总体方差应约为25") assert.InEpsilon(t, 50.0, sampleVar, 0.001, "sensor2的样本方差应约为50") } } - //fmt.Println("统计聚合函数测试完成") + //fmt.Println("Statistical aggregation function testing completed") } func TestDeduplicateAggregateInSQL(t *testing.T) { @@ -492,70 +492,70 @@ func TestDeduplicateAggregateInSQL(t *testing.T) { ssql := streamsql.New() defer ssql.Stop() - // 测试使用去重聚合函数的SQL查询 + // Testing SQL queries using deduplicated aggregation functions var rsql = "SELECT device, deduplicate(status) as unique_status FROM stream GROUP BY device, TumblingWindow('1s')" err := ssql.Execute(rsql) assert.Nil(t, err) strm := ssql - //fmt.Println("开始测试去重聚合函数功能") + //fmt.Println("Start testing the de-duplication aggregation function") - // 添加测试数据,包含重复的状态 - // 不使用事件时间,不需要时间戳字段 + // Add test data containing duplicate states + // No event time is used, and no timestamp field is needed testData := []map[string]any{ {"device": "sensor1", "status": "good"}, - {"device": "sensor1", "status": "good"}, // 重复 + {"device": "sensor1", "status": "good"}, // Repeat {"device": "sensor1", "status": "warning"}, - {"device": "sensor1", "status": "good"}, // 重复 + {"device": "sensor1", "status": "good"}, // Repeat {"device": "sensor2", "status": "error"}, - {"device": "sensor2", "status": "error"}, // 重复 + {"device": "sensor2", "status": "error"}, // Repeat {"device": "sensor2", "status": "ok"}, } - // 添加数据 - //fmt.Println("添加测试数据") + // Add data + //fmt.Println("Add test data") for _, data := range testData { strm.Emit(data) } - // 创建结果接收通道 + // Create a result receiving channel resultChan := make(chan any, 10) - // 添加结果回调 + // Add result callbacks strm.AddSink(func(result []map[string]any) { - //fmt.Printf("接收到结果: %v\n", result) + //fmt.Printf("Result received: %v\n", result) resultChan <- result }) - // 等待窗口触发(处理时间模式) - //fmt.Println("等待窗口初始化...") + // Wait window trigger (processing time mode) + //fmt.Println("Waiting for window initialization...") time.Sleep(1 * time.Second) - // 手动触发窗口 - //fmt.Println("手动触发窗口") + // Manually trigger the window + //fmt.Println("Manually trigger the window") strm.TriggerWindow() - // 等待结果 + // Wait for the results ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) defer cancel() var actual any select { case actual = <-resultChan: - //fmt.Println("成功接收到结果") + //fmt.Println("Successfully received the results") cancel() case <-ctx.Done(): - t.Fatal("测试超时,未收到结果") + t.Fatal("The test timed out and no results were received") } - // 验证结果 + // Verify the results resultSlice, ok := actual.([]map[string]any) require.True(t, ok, "结果应该是[]map[string]any类型") - // 验证结果数量 + // Verification of the number of results assert.Len(t, resultSlice, 2, "应该有2个设备的聚合结果") - // 检查设备及其去重结果 + // Check the equipment and its duplication results for _, result := range resultSlice { device, _ := result["device"].(string) uniqueStatus, ok := result["unique_status"] @@ -563,14 +563,14 @@ func TestDeduplicateAggregateInSQL(t *testing.T) { assert.True(t, ok, "unique_status应该存在") if device == "sensor1" { - // sensor1应该有去重后的状态:["good", "warning"] + // sensor1 should have a deduplicated state: ["good", "warning"] statusArray, ok := uniqueStatus.([]any) assert.True(t, ok, "unique_status应该是数组") assert.Len(t, statusArray, 2, "sensor1应该有2个不同的状态") assert.Contains(t, statusArray, "good") assert.Contains(t, statusArray, "warning") } else if device == "sensor2" { - // sensor2应该有去重后的状态:["error", "ok"] + // sensor2 should have a deduplicated state: ["error", "ok"] statusArray, ok := uniqueStatus.([]any) assert.True(t, ok, "unique_status应该是数组") assert.Len(t, statusArray, 2, "sensor2应该有2个不同的状态") @@ -579,7 +579,7 @@ func TestDeduplicateAggregateInSQL(t *testing.T) { } } - //fmt.Println("去重聚合函数测试完成") + //fmt.Println("The de-deduplication aggregation function test is complete") } func TestExprAggregationFunctions(t *testing.T) { @@ -587,7 +587,7 @@ func TestExprAggregationFunctions(t *testing.T) { ssql := streamsql.New() defer ssql.Stop() - // 测试使用表达式运算的聚合函数SQL查询 + // Test an aggregate function SQL query using expression operations var rsql = `SELECT device, avg(temperature * 1.8 + 32) as avg_fahrenheit, @@ -604,66 +604,66 @@ func TestExprAggregationFunctions(t *testing.T) { assert.Nil(t, err) strm := ssql - //fmt.Println("开始测试表达式聚合函数功能") + //fmt.Println("Start testing the expression aggregation function feature") - // 添加测试数据 - // 不使用事件时间,不需要时间戳字段 + // Add test data + // No event time is used, and no timestamp field is needed testData := []map[string]any{ - // device1的数据 - {"device": "device1", "temperature": 20.0, "humidity": 60.0, "status": "normal"}, // 华氏度=68, 偏差=0, 和=80 - {"device": "device1", "temperature": 25.0, "humidity": 65.0, "status": "warning"}, // 华氏度=77, 偏差=10, 和=90 - {"device": "device1", "temperature": 30.0, "humidity": 70.0, "status": "normal"}, // 华氏度=86, 偏差=20, 和=100 - - // device2的数据 - {"device": "device2", "temperature": 15.0, "humidity": 55.0, "status": "error"}, // 华氏度=59, 偏差=-10, 和=70 - {"device": "device2", "temperature": 18.0, "humidity": 58.0, "status": "normal"}, // 华氏度=64.4, 偏差=-4, 和=76 - {"device": "device2", "temperature": 22.0, "humidity": 62.0, "status": "error"}, // 华氏度=71.6, 偏差=4, 和=84 + // device1 + {"device": "device1", "temperature": 20.0, "humidity": 60.0, "status": "normal"}, // Fahrenheit = 68, deviation = 0, sum = 80 + {"device": "device1", "temperature": 25.0, "humidity": 65.0, "status": "warning"}, // Fahrenheit = 77, Deviation = 10, SUM = 90 + {"device": "device1", "temperature": 30.0, "humidity": 70.0, "status": "normal"}, // Fahrenheit = 86, deviation = 20, sum = 100 + + // device2 + {"device": "device2", "temperature": 15.0, "humidity": 55.0, "status": "error"}, // Fahrenheit = 59, deviation = -10, sum = 70 + {"device": "device2", "temperature": 18.0, "humidity": 58.0, "status": "normal"}, // Fahrenheit = 64.4, deviation = -4, sum = 76 + {"device": "device2", "temperature": 22.0, "humidity": 62.0, "status": "error"}, // Fahrenheit = 71.6, deviation = 4, sum = 84 } - // 添加数据 - //fmt.Println("添加测试数据") + // Add data + //fmt.Println("Add test data") for _, data := range testData { strm.Emit(data) } - // 创建结果接收通道 + // Create a result receiving channel resultChan := make(chan any, 10) - // 添加结果回调 + // Add result callbacks strm.AddSink(func(result []map[string]any) { - //fmt.Printf("接收到结果: %v\n", result) + //fmt.Printf("Result received: %v\n", result) resultChan <- result }) - // 等待窗口触发(处理时间模式) - //fmt.Println("等待窗口初始化...") + // Wait window trigger (processing time mode) + //fmt.Println("Waiting for window initialization...") time.Sleep(1 * time.Second) - // 手动触发窗口 - //fmt.Println("手动触发窗口") + // Manually trigger the window + //fmt.Println("Manually trigger the window") strm.TriggerWindow() - // 等待结果 + // Wait for the results ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) defer cancel() var actual any select { case actual = <-resultChan: - //fmt.Println("成功接收到结果") + //fmt.Println("Successfully received the results") cancel() case <-ctx.Done(): - t.Fatal("测试超时,未收到结果") + t.Fatal("The test timed out and no results were received") } - // 验证结果 + // Verify the results resultSlice, ok := actual.([]map[string]any) require.True(t, ok, "结果应该是[]map[string]any类型") - // 验证结果数量 + // Verification of the number of results assert.Len(t, resultSlice, 2, "应该有2个设备的聚合结果") - // 检查设备及其计算结果 + // Inspect the equipment and its calculation results for _, result := range resultSlice { device, _ := result["device"].(string) avgFahrenheit, ok1 := result["avg_fahrenheit"].(float64) @@ -683,17 +683,17 @@ func TestExprAggregationFunctions(t *testing.T) { assert.True(t, ok7, "unique_status_device应该存在") if device == "device1" { - // device1的验证 - // 平均华氏度: (68 + 77 + 86) / 3 = 77 + // Verification of device1 + // Average Fahrenheit: (68 + 77 + 86) / 3 = 77 assert.InEpsilon(t, 77.0, avgFahrenheit, 0.1, "device1的平均华氏度应约为77") - // 温度偏差标准差: sqrt(((0-10)² + (10-10)² + (20-10)²) / 2) = sqrt(200/2) = 10 + // Temperature deviation standard deviation: sqrt(((0-10)² + (10-10)² + (20-10)²) / 2) = sqrt(200/2) = 10 assert.InEpsilon(t, 10.0, tempStddev, 0.1, "device1的温度偏差标准差应约为10") - // 温度除以10的方差: ((2-2.5)² + (2.5-2.5)² + (3-2.5)²) / 3 = 0.167 + // Temperature divided by 10 variance: ((2-2.5)² + (2.5-2.5)² + (3-2.5)²) / 3 = 0.167 assert.InEpsilon(t, 0.167, tempVar, 0.01, "device1的温度方差应约为0.167") - // 温度和湿度的和数组 + // Temperature and humidity arrays tempHumSumArray, ok := tempHumSum.([]any) assert.True(t, ok, "temp_hum_sum应该是数组") assert.Len(t, tempHumSumArray, 3, "device1应该有3个温度和湿度的和") @@ -701,14 +701,14 @@ func TestExprAggregationFunctions(t *testing.T) { assert.Contains(t, tempHumSumArray, 90.0) assert.Contains(t, tempHumSumArray, 100.0) - // 最后一个温度和湿度的乘积: 30 * 70 = 2100 + // Finally, the product of temperature and humidity: 30 * 70 = 2100 assert.InEpsilon(t, 2100.0, lastTempHum, 0.1, "device1的最后一个温度和湿度乘积应为2100") - // 设备状态组合 + // Equipment status combinations assert.Contains(t, deviceStatus, "device1_normal") assert.Contains(t, deviceStatus, "device1_warning") - // 状态设备组合去重 + // State equipment combination deduplication uniqueArray, ok := uniqueStatusDevice.([]any) assert.True(t, ok, "unique_status_device应该是数组") assert.Len(t, uniqueArray, 2, "device1应该有2个不同的状态设备组合") @@ -716,17 +716,17 @@ func TestExprAggregationFunctions(t *testing.T) { assert.Contains(t, uniqueArray, "warning_device1") } else if device == "device2" { - // device2的验证 - // 平均华氏度: (59 + 64.4 + 71.6) / 3 = 65 + // Validation of device2 + // Average Fahrenheit: (59 + 64.4 + 71.6) / 3 = 65 assert.InEpsilon(t, 65.0, avgFahrenheit, 0.1, "device2的平均华氏度应约为65") - // 温度偏差标准差: sqrt(((-10-(-3.33))² + (-4-(-3.33))² + (4-(-3.33))²) / 2) = sqrt(147.33/2) = 7.023 + // Temperature deviation standard deviation: sqrt(((-10 - (-3.33))² + (-4 - (-3.33))² + (4 - (-3.33))²) / 2) = sqrt(147.33/2) = 7.023 assert.InEpsilon(t, 7.023, tempStddev, 0.1, "device2的温度偏差标准差应约为7.023") - // 温度除以10的方差: ((1.5-1.83)² + (1.8-1.83)² + (2.2-1.83)²) / 3 = 0.082 + // Temperature divided by 10 variance: ((1.5 - 1.83)² + (1.8 - 1.83)² + (2.2 - 1.83)²) / 3 = 0.082 assert.InEpsilon(t, 0.082, tempVar, 0.01, "device2的温度方差应约为0.082") - // 温度和湿度的和数组 + // Temperature and humidity arrays tempHumSumArray, ok := tempHumSum.([]any) assert.True(t, ok, "temp_hum_sum应该是数组") assert.Len(t, tempHumSumArray, 3, "device2应该有3个温度和湿度的和") @@ -734,14 +734,14 @@ func TestExprAggregationFunctions(t *testing.T) { assert.Contains(t, tempHumSumArray, 76.0) assert.Contains(t, tempHumSumArray, 84.0) - // 最后一个温度和湿度的乘积: 22 * 62 = 1364 + // Finally, the product of temperature and humidity: 22 * 62 = 1364 assert.InEpsilon(t, 1364.0, lastTempHum, 0.1, "device2的最后一个温度和湿度乘积应为1364") - // 设备状态组合 + // Equipment status combinations assert.Contains(t, deviceStatus, "device2_error") assert.Contains(t, deviceStatus, "device2_normal") - // 状态设备组合去重 + // State equipment combination deduplication uniqueArray, ok := uniqueStatusDevice.([]any) assert.True(t, ok, "unique_status_device应该是数组") assert.Len(t, uniqueArray, 2, "device2应该有2个不同的状态设备组合") @@ -750,7 +750,7 @@ func TestExprAggregationFunctions(t *testing.T) { } } - //fmt.Println("表达式聚合函数测试完成") + //fmt.Println("The expression aggregation function test is complete") } func TestAnalyticalFunctionsInSQL(t *testing.T) { @@ -758,70 +758,70 @@ func TestAnalyticalFunctionsInSQL(t *testing.T) { ssql := streamsql.New() defer ssql.Stop() - // 测试使用分析函数的SQL查询 + // Testing SQL queries using analysis functions var rsql = "SELECT device, lag(temperature) as prev_temp, latest(temperature) as current_temp, had_changed(temperature) as temp_changed FROM stream GROUP BY device, TumblingWindow('1s')" err := ssql.Execute(rsql) if err != nil { - t.Skipf("v1.2 分析函数改为直连 OVER 语义,不再与 GROUP BY/窗口混用;窗口+OVER WHEN 见后续版本: %v", err) + t.Skipf("v1.2 The analysis function has been changed to direct OVER semantics, no longer used interchangeably with the GROUP BY/ window; Window + OVER WHEN See subsequent versions: %v", err) } strm := ssql - //fmt.Println("开始测试分析函数功能") + //fmt.Println("Start testing the analysis function features") - // 添加测试数据 - // 不使用事件时间,不需要时间戳字段 + // Add test data + // No event time is used, and no timestamp field is needed testData := []map[string]any{ {"device": "sensor1", "temperature": 20.0}, {"device": "sensor1", "temperature": 25.0}, - {"device": "sensor1", "temperature": 25.0}, // 重复值,测试had_changed + {"device": "sensor1", "temperature": 25.0}, // Repeat values, test had_changed {"device": "sensor2", "temperature": 18.0}, {"device": "sensor2", "temperature": 22.0}, } - // 添加数据 - //fmt.Println("添加测试数据") + // Add data + //fmt.Println("Add test data") for _, data := range testData { strm.Emit(data) } - // 创建结果接收通道 + // Create a result receiving channel resultChan := make(chan any, 10) - // 添加结果回调 + // Add result callbacks strm.AddSink(func(result []map[string]any) { - //fmt.Printf("接收到结果: %v\n", result) + //fmt.Printf("Result received: %v\n", result) resultChan <- result }) - // 等待窗口触发(处理时间模式) - //fmt.Println("等待窗口初始化...") + // Wait window trigger (processing time mode) + //fmt.Println("Waiting for window initialization...") time.Sleep(1 * time.Second) - // 手动触发窗口 - //fmt.Println("手动触发窗口") + // Manually trigger the window + //fmt.Println("Manually trigger the window") strm.TriggerWindow() - // 等待结果 + // Wait for the results ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) defer cancel() var actual any select { case actual = <-resultChan: - //fmt.Println("成功接收到结果") + //fmt.Println("Successfully received the results") cancel() case <-ctx.Done(): - t.Fatal("测试超时,未收到结果") + t.Fatal("The test timed out and no results were received") } - // 验证结果 + // Verify the results resultSlice, ok := actual.([]map[string]any) require.True(t, ok, "结果应该是[]map[string]any类型") - // 验证结果数量 + // Verification of the number of results assert.Len(t, resultSlice, 2, "应该有2个设备的聚合结果") - // 检查设备及其分析函数结果 + // Check the equipment and its analysis function results for _, result := range resultSlice { device, _ := result["device"].(string) @@ -830,16 +830,16 @@ func TestAnalyticalFunctionsInSQL(t *testing.T) { assert.Contains(t, result, "temp_changed", "结果应包含temp_changed字段") if device == "sensor1" { - // sensor1有3个温度值: 20.0, 25.0, 25.0 - // latest应该返回最新值 + // sensor1 has three temperature values: 20.0, 25.0, and 25.0 + // The last test should return the latest value currentTemp := result["current_temp"] assert.NotNil(t, currentTemp, "current_temp不应为空") - // had_changed应该有变化记录 + // had_changed should have a record of changes tempChanged := result["temp_changed"] assert.NotNil(t, tempChanged, "temp_changed不应为空") } else if device == "sensor2" { - // sensor2有2个温度值: 18.0, 22.0 + // The sensor2 has two temperature values: 18.0 and 22.0 currentTemp := result["current_temp"] assert.NotNil(t, currentTemp, "current_temp不应为空") @@ -848,104 +848,104 @@ func TestAnalyticalFunctionsInSQL(t *testing.T) { } } - //fmt.Println("分析函数测试完成") + //fmt.Println("Analysis function testing completed") } func TestHadChangedFunctionInSQL(t *testing.T) { t.Parallel() ssql := streamsql.New() defer ssql.Stop() - // 测试had_changed函数的SQL查询 + // SQL queries for had_changed test functions var rsql = "SELECT device, had_changed(temperature) as temp_changed FROM stream GROUP BY device, TumblingWindow('1s')" err := ssql.Execute(rsql) if err != nil { - t.Skipf("v1.2 分析函数改为直连 OVER 语义,不再与 GROUP BY/窗口混用;窗口+OVER WHEN 见后续版本: %v", err) + t.Skipf("v1.2 The analysis function has been changed to direct OVER semantics, no longer used interchangeably with the GROUP BY/ window; Window + OVER WHEN See subsequent versions: %v", err) } strm := ssql - //fmt.Println("开始测试had_changed函数功能") + //fmt.Println("Start testing had_changed function functions") - // 添加测试数据 - 包含重复值和变化值 - // 不使用事件时间,不需要时间戳字段 + // Add test data – including duplicate and variant values + // No event time is used, and no timestamp field is needed testData := []map[string]any{ {"device": "monitor", "temperature": 20.0}, - {"device": "monitor", "temperature": 20.0}, // 相同值 - {"device": "monitor", "temperature": 25.0}, // 变化值 - {"device": "monitor", "temperature": 25.0}, // 相同值 - {"device": "monitor", "temperature": 30.0}, // 变化值 + {"device": "monitor", "temperature": 20.0}, // Same value + {"device": "monitor", "temperature": 25.0}, // Change values + {"device": "monitor", "temperature": 25.0}, // Same value + {"device": "monitor", "temperature": 30.0}, // Change values } - // 添加数据 - //fmt.Println("添加测试数据") + // Add data + //fmt.Println("Add test data") for _, data := range testData { strm.Emit(data) } - // 创建结果接收通道 + // Create a result receiving channel resultChan := make(chan any, 10) - // 添加结果回调 + // Add result callbacks strm.AddSink(func(result []map[string]any) { - //fmt.Printf("接收到结果: %v\n", result) + //fmt.Printf("Result received: %v\n", result) resultChan <- result }) - // 等待窗口触发(处理时间模式) - //fmt.Println("等待窗口初始化...") + // Wait window trigger (processing time mode) + //fmt.Println("Waiting for window initialization...") time.Sleep(1 * time.Second) - // 手动触发窗口 - //fmt.Println("手动触发窗口") + // Manually trigger the window + //fmt.Println("Manually trigger the window") strm.TriggerWindow() - // 等待结果 + // Wait for the results ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) defer cancel() var actual any select { case actual = <-resultChan: - //fmt.Println("成功接收到结果") + //fmt.Println("Successfully received the results") cancel() case <-ctx.Done(): - t.Fatal("测试超时,未收到结果") + t.Fatal("The test timed out and no results were received") } - // 验证结果 + // Verify the results resultSlice, ok := actual.([]map[string]any) require.True(t, ok, "结果应该是[]map[string]any类型") - // 验证结果数量 + // Verification of the number of results assert.Len(t, resultSlice, 1, "应该有1个设备的聚合结果") result := resultSlice[0] device, _ := result["device"].(string) assert.Equal(t, "monitor", device, "设备名应该正确") - // 验证字段存在 + // Verify the presence of fields assert.Contains(t, result, "temp_changed", "结果应包含temp_changed字段") - // had_changed函数应该返回布尔值 + // had_changed function should return a boolean value //tempChanged := result["temp_changed"] - //fmt.Printf("had_changed函数返回值: %v\n", tempChanged) + //fmt.Printf("had_changed function return value: %v\n", tempChanged) - //fmt.Println("had_changed函数测试完成") + //fmt.Println("had_changed function test completed") } func TestIncrementalComputationBasic(t *testing.T) { t.Parallel() ssql := streamsql.New() defer ssql.Stop() - // 测试基本的增量计算聚合函数 + // Test the basic incremental calculation of the aggregation function var rsql = "SELECT device, sum(temperature) as total, avg(temperature) as average, count(*) as cnt FROM stream GROUP BY device, TumblingWindow('1s')" err := ssql.Execute(rsql) assert.Nil(t, err) strm := ssql - //fmt.Println("开始测试基本增量计算功能") + //fmt.Println("Start testing the basic incremental calculation function") - // 添加测试数据 - // 不使用事件时间,不需要时间戳字段 + // Add test data + // No event time is used, and no timestamp field is needed testData := []map[string]any{ {"device": "sensor1", "temperature": 10.0}, {"device": "sensor1", "temperature": 20.0}, @@ -954,57 +954,57 @@ func TestIncrementalComputationBasic(t *testing.T) { {"device": "sensor2", "temperature": 25.0}, } - // 添加数据 - //fmt.Println("添加测试数据") + // Add data + //fmt.Println("Add test data") for _, data := range testData { strm.Emit(data) } - // 创建结果接收通道 + // Create a result receiving channel resultChan := make(chan any, 10) - // 添加结果回调 + // Add result callbacks strm.AddSink(func(result []map[string]any) { - //fmt.Printf("接收到结果: %v\n", result) + //fmt.Printf("Result received: %v\n", result) resultChan <- result }) - // 等待窗口触发(处理时间模式) - //fmt.Println("等待窗口初始化...") + // Wait window trigger (processing time mode) + //fmt.Println("Waiting for window initialization...") time.Sleep(1 * time.Second) - // 手动触发窗口 - //fmt.Println("手动触发窗口") + // Manually trigger the window + //fmt.Println("Manually trigger the window") strm.TriggerWindow() - // 等待结果 + // Wait for the results ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) defer cancel() var actual any select { case actual = <-resultChan: - //fmt.Println("成功接收到结果") + //fmt.Println("Successfully received the results") cancel() case <-ctx.Done(): - t.Fatal("测试超时,未收到结果") + t.Fatal("The test timed out and no results were received") } - // 验证结果 + // Verify the results resultSlice, ok := actual.([]map[string]any) require.True(t, ok, "结果应该是[]map[string]any类型") - // 验证结果数量 + // Verification of the number of results assert.Len(t, resultSlice, 2, "应该有2个设备的聚合结果") - // 检查设备及其聚合结果 + // Inspect the equipment and its aggregated results for _, result := range resultSlice { device, _ := result["device"].(string) total := result["total"] average := result["average"] count := result["cnt"] - //fmt.Printf("设备 %s: total=%v, average=%v, count=%v\n", device, total, average, count) + //fmt.Printf("Equipment %s: total=%v, average=%v, count=%v\n", device, total, average, count) if device == "sensor1" { // sensor1: sum=60, avg=20, count=3 @@ -1031,41 +1031,41 @@ func TestIncrementalComputationBasic(t *testing.T) { } } - //fmt.Println("基本增量计算测试完成") + //fmt.Println("Basic incremental calculation test completed") } -// TestExprFunctions 测试expr函数的使用 +// TestExprFunctions Tests the use of the expr function func TestExprFunctions(t *testing.T) { t.Parallel() ssql := streamsql.New() defer ssql.Stop() - // 测试基本expr函数:字符串处理 + // Testing basic expr function: string processing var rsql = "SELECT device, upper(device) as upper_device, lower(device) as lower_device FROM stream" err := ssql.Execute(rsql) assert.Nil(t, err) strm := ssql - // 创建结果接收通道 + // Create a result receiving channel resultChan := make(chan any, 10) - // 添加结果回调 + // Add result callbacks strm.AddSink(func(result []map[string]any) { resultChan <- result }) - // 添加测试数据 + // Add test data testData := []map[string]any{ {"device": "SensorA"}, {"device": "SensorB"}, } - // 添加数据 + // Add data for _, data := range testData { strm.Emit(data) } - // 等待结果 + // Wait for the results var results []any var resultsMutex sync.Mutex timeout := time.After(2 * time.Second) @@ -1090,7 +1090,7 @@ func TestExprFunctions(t *testing.T) { } } - // 验证结果 + // Verify the results resultsMutex.Lock() finalResultCount := len(results) resultsCopy := make([]any, len(results)) @@ -1108,28 +1108,28 @@ func TestExprFunctions(t *testing.T) { upperDevice, _ := item["upper_device"].(string) lowerDevice, _ := item["lower_device"].(string) - // 验证upper函数 + // Verify the UPPER function assert.Equal(t, strings.ToUpper(device), upperDevice, "upper函数应该正确转换大写") - // 验证lower函数 + // Verify the lower function assert.Equal(t, strings.ToLower(device), lowerDevice, "lower函数应该正确转换小写") } } } -// TestExprFunctionsInAggregation 测试在聚合中使用expr函数 +// TestExprFunctionsInAggregation tests using the appr function in aggregation func TestExprFunctionsInAggregation(t *testing.T) { t.Parallel() ssql := streamsql.New() defer ssql.Stop() - // 测试在聚合函数中使用expr函数:数学计算 + // Test the use of expr in aggregate functions: mathematical calculations var rsql = "SELECT device, AVG(abs(temperature - 25)) as avg_deviation, MAX(ceil(temperature)) as max_ceil FROM stream GROUP BY device, TumblingWindow('1s')" err := ssql.Execute(rsql) assert.Nil(t, err) strm := ssql - // 添加测试数据 - // 不使用事件时间,不需要时间戳字段 + // Add test data + // No event time is used, and no timestamp field is needed testData := []map[string]any{ {"device": "sensor1", "temperature": 23.5}, // abs(23.5-25) = 1.5, ceil(23.5) = 24 {"device": "sensor1", "temperature": 26.8}, // abs(26.8-25) = 1.8, ceil(26.8) = 27 @@ -1137,26 +1137,26 @@ func TestExprFunctionsInAggregation(t *testing.T) { {"device": "sensor2", "temperature": 25.9}, // abs(25.9-25) = 0.9, ceil(25.9) = 26 } - // 创建结果接收通道 + // Create a result receiving channel resultChan := make(chan any, 10) - // 添加结果回调 + // Add result callbacks strm.AddSink(func(result []map[string]any) { resultChan <- result }) - // 添加数据 + // Add data for _, data := range testData { strm.Emit(data) } - // 等待窗口初始化 + // Wait for the window to initialize time.Sleep(1 * time.Second) - // 手动触发窗口 + // Manually trigger the window strm.TriggerWindow() - // 等待结果 + // Wait for the results ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) defer cancel() @@ -1165,15 +1165,15 @@ func TestExprFunctionsInAggregation(t *testing.T) { case actual = <-resultChan: cancel() case <-ctx.Done(): - t.Fatal("测试超时,未收到结果") + t.Fatal("The test timed out and no results were received") } - // 验证结果 + // Verify the results resultSlice, ok := actual.([]map[string]any) require.True(t, ok, "结果应该是[]map[string]any类型") assert.Len(t, resultSlice, 2, "应该有2个设备的聚合结果") - // 检查聚合结果 + // Check the aggregate results for _, result := range resultSlice { device, _ := result["device"].(string) avgDeviation, ok := result["avg_deviation"].(float64) @@ -1195,39 +1195,39 @@ func TestExprFunctionsInAggregation(t *testing.T) { } } -// TestNestedExprFunctions 测试嵌套expr函数调用 +// TestNestedExprFunctions tests nested expr function calls func TestNestedExprFunctions(t *testing.T) { t.Parallel() ssql := streamsql.New() defer ssql.Stop() - // 测试嵌套函数:字符串处理 + 数组操作 + // Test nested functions: string handling + array operations var rsql = "SELECT device, len(split(upper(device), 'SENSOR')) as split_count FROM stream" err := ssql.Execute(rsql) assert.Nil(t, err) strm := ssql - // 创建结果接收通道 + // Create a result receiving channel resultChan := make(chan any, 10) - // 添加结果回调 + // Add result callbacks strm.AddSink(func(result []map[string]any) { resultChan <- result }) - // 添加测试数据 + // Add test data testData := []map[string]any{ {"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 } - // 添加数据 + // Add data for _, data := range testData { strm.Emit(data) } - // 等待结果 + // Wait for the results var results []any var resultsMutex sync.Mutex timeout := time.After(2 * time.Second) @@ -1252,7 +1252,7 @@ func TestNestedExprFunctions(t *testing.T) { } } - // 验证结果 + // Verify the results resultsMutex.Lock() finalResultCount := len(results) resultsCopy := make([]any, len(results)) @@ -1275,7 +1275,7 @@ func TestNestedExprFunctions(t *testing.T) { } } - // 验证嵌套函数调用结果 + // Verify the nested function call result if count, exists := deviceResults["sensor1"]; exists { assert.Equal(t, 2.0, count, "sensor1经过嵌套函数处理后应该得到2") } @@ -1287,38 +1287,38 @@ func TestNestedExprFunctions(t *testing.T) { } } -// TestExprFunctionsWithStreamSQLFunctions 测试expr函数与StreamSQL函数混合使用 +// TestExprFunctionsWithStreamSQLFunctions Test expr functions are used in combination with StreamSQL functions func TestExprFunctionsWithStreamSQLFunctions(t *testing.T) { t.Parallel() ssql := streamsql.New() defer ssql.Stop() - // 测试混合使用:StreamSQL的concat函数 + expr的upper函数 + // Test a hybrid use: StreamSQL concat function + expr upper function var rsql = "SELECT device, concat(upper(device), '_processed') as processed_name FROM stream" err := ssql.Execute(rsql) assert.Nil(t, err) strm := ssql - // 创建结果接收通道 + // Create a result receiving channel resultChan := make(chan any, 10) - // 添加结果回调 + // Add result callbacks strm.AddSink(func(result []map[string]any) { resultChan <- result }) - // 添加测试数据 + // Add test data testData := []map[string]any{ {"device": "sensor1"}, {"device": "device2"}, } - // 添加数据 + // Add data for _, data := range testData { strm.Emit(data) } - // 等待结果 + // Wait for the results var results []any var resultsMutex sync.Mutex timeout := time.After(2 * time.Second) @@ -1343,7 +1343,7 @@ func TestExprFunctionsWithStreamSQLFunctions(t *testing.T) { } } - // 验证结果 + // Verify the results resultsMutex.Lock() finalResultCount := len(results) resultsCopy := make([]any, len(results)) @@ -1360,11 +1360,11 @@ func TestExprFunctionsWithStreamSQLFunctions(t *testing.T) { device, _ := item["device"].(string) processedName, _ := item["processed_name"].(string) - // 验证混合函数调用结果 + // Verify the result of the hybrid function call expected := strings.ToUpper(device) + "_processed" assert.Equal(t, expected, processedName, "混合函数调用应该正确处理") } } } -// TestSelectAllFeature 专门测试SELECT *功能 +// TestSelectAllFeature is specifically designed to test the SELECT* function diff --git a/test/e2e/function_scenarios_test.go b/test/e2e/function_scenarios_test.go index c14c7c9..9207e16 100644 --- a/test/e2e/function_scenarios_test.go +++ b/test/e2e/function_scenarios_test.go @@ -10,11 +10,11 @@ import ( "github.com/stretchr/testify/require" ) -// function_scenarios_test.go 补充内置函数的端到端 SQL 集成覆盖。 -// 复用 analytic_parity_test.go 中的 runDirect / runWindow / assertRows / toFloatVal 等助手, -// 对每个函数至少给出确定值的主路径断言,并在有意义处补充边界用例。 +// function_scenarios_test.go supplements the end-to-end SQL integration coverage of built-in functions. +// Reuse assistants like runDirect / runWindow / assertRows / toFloatVal from analytic_parity_test.go, +// Provide at least a definite main path assertion for each function, and supplement boundary use cases where they are meaningful. -// emitRow 执行单行并返回结果与错误,不在错误时 fatal(供边界用例观察错误传播)。 +// emitRow executes a single line and returns results and errors, not fatal when errors occur (for boundary use cases to observe error propagation). func emitRow(t *testing.T, sql string, row map[string]any) (map[string]any, error) { t.Helper() ssql := streamsql.New() @@ -25,7 +25,7 @@ func emitRow(t *testing.T, sql string, row map[string]any) (map[string]any, erro return ssql.EmitSync(row) } -// scalarVal 取结果行中唯一(别名)字段的值。 +// scalarVal retrieves the value of the unique (alias) field in the result row. func scalarVal(row map[string]any) any { for _, v := range row { return v @@ -33,7 +33,7 @@ func scalarVal(row map[string]any) any { return nil } -// numEq 容忍 int/int64/float64 的精确数值比较(用于结果类型随字面量类型变化的情况)。 +// numEq tolerates precise numerical comparisons of int/int64/float64 (used when the result type varies with literal type). func numEq(t *testing.T, name string, got any, want float64) { t.Helper() g := toFloatVal(got) @@ -42,7 +42,7 @@ func numEq(t *testing.T, name string, got any, want float64) { } } -// numApprox 同 numEq,但带相对/绝对容差(用于三角/对数等浮点函数)。 +// numApprox is the same as numEq, but with relative and absolute tolerance (used for floating-point functions such as trigonometric and logarithmic). func numApprox(t *testing.T, name string, got any, want float64, tol float64) { t.Helper() g := toFloatVal(got) @@ -51,7 +51,7 @@ func numApprox(t *testing.T, name string, got any, want float64, tol float64) { } } -// anySliceEq 比较 got 是否为 []any 且与 want 元素逐一相等;数值元素按 float64 归一比较。 +// anySliceEq compares whether got is []any and equal to the want element one by one; Numeric elements are compared using float64 as normalized. func anySliceEq(got any, want []any) bool { g, ok := got.([]any) if !ok || len(g) != len(want) { @@ -61,7 +61,7 @@ func anySliceEq(got any, want []any) bool { if g[i] == want[i] { continue } - // 数值类型归一为 float64 后比较。 + // After normalizing the numeric type to float64, compare it. if toFloatVal(g[i]) != 0 || toFloatVal(want[i]) != 0 { if toFloatVal(g[i]) == toFloatVal(want[i]) { continue @@ -81,13 +81,13 @@ func TestFunctionScenarios_DateTime(t *testing.T) { t.Parallel() row, err := emitRow(t, "SELECT now() AS ts FROM stream", map[string]any{"x": 1}) require.NoError(t, err) - // now() 返回 time.Time。 + // now() returns time.Time. ts, ok := row["ts"].(time.Time) require.True(t, ok, "now() should return time.Time, got %T", row["ts"]) assert.True(t, ts.After(time.Date(2024, 1, 1, 0, 0, 0, 0, time.UTC)), "now()=%v should be after 2024-01-01", ts) }) - // 回归:to_seconds(now()) 与 unix_timestamp() 取当前 Unix 秒,与 created_at(微秒)求差。 + // Regression: to_seconds(now()) and unix_timestamp() take the current Unix second, and find the difference between created_at (microseconds). t.Run("to_seconds_now_filter_le", func(t *testing.T) { t.Parallel() sql := "SELECT 'hit' AS result, abs(created_at/1000000 - to_seconds(now())) AS ts FROM stream WHERE abs(created_at/1000000 - to_seconds(now())) <= 100.0" @@ -166,7 +166,7 @@ func TestFunctionScenarios_DateTime(t *testing.T) { require.Len(t, got, 1) tt, ok := got[0]["d"].(time.Time) require.True(t, ok, "convert_tz should return time.Time, got %T", got[0]["d"]) - // 输入按 UTC 解析,+08:00 后为 18:00:00。 + // Enter by UTC parsing; after +08:00, it becomes 18:00:00. assert.Equal(t, "2024-01-15 18:00:00", tt.Format("2006-01-02 15:04:05")) }) @@ -186,7 +186,7 @@ func TestFunctionScenarios_DateTime(t *testing.T) { t.Run("day_hour_dayofweek_dayofyear_extract", func(t *testing.T) { t.Parallel() - // 2024-01-15 是周一(Go Weekday=1)。 + // 2024-01-15 is a Monday (Go Weekday=1). got := runDirect(t, `SELECT day('2024-01-15 10:30:00') AS dy, hour('2024-01-15 10:30:00') AS hr, dayofweek('2024-01-15 10:30:00') AS dow, dayofyear('2024-01-15 10:30:00') AS doy, @@ -250,7 +250,7 @@ func TestFunctionScenarios_JSON(t *testing.T) { t.Run("json_extract_missing_path_nil", func(t *testing.T) { t.Parallel() - // 缺失路径应优雅返回 nil,而非报错。 + // Missing paths should elegantly return nil rather than errors. row, err := emitRow(t, `SELECT json_extract(j, '$.missing') AS v FROM stream`, map[string]any{"j": `{"a":1}`}) require.NoError(t, err) @@ -285,7 +285,7 @@ func TestFunctionScenarios_Array(t *testing.T) { `SELECT array_position(tags, 'b') AS p, array_position(tags, 'z') AS miss FROM stream`, []map[string]any{{"tags": []any{"a", "b", "c"}}}) require.Len(t, got, 1) - numEq(t, "array_position hit", got[0]["p"], 2) // 1-based + numEq(t, "array_position hit", got[0]["p"], 2) // 1-based numEq(t, "array_position miss", got[0]["miss"], 0) // not found }) @@ -346,10 +346,10 @@ func TestFunctionScenarios_Bitwise(t *testing.T) { `SELECT bitand(12, 10) AS a, bitor(12, 10) AS o, bitxor(12, 10) AS x, bitnot(0) AS n FROM stream`, []map[string]any{{"x": 1}}) require.Len(t, got, 1) - numEq(t, "bitand", got[0]["a"], 8) // 1100 & 1010 = 1000 - numEq(t, "bitor", got[0]["o"], 14) // 1100 | 1010 = 1110 - numEq(t, "bitxor", got[0]["x"], 6) // 1100 ^ 1010 = 0110 - numEq(t, "bitnot", got[0]["n"], -1) // ^0 = -1 + numEq(t, "bitand", got[0]["a"], 8) // 1100 & 1010 = 1000 + numEq(t, "bitor", got[0]["o"], 14) // 1100 | 1010 = 1110 + numEq(t, "bitxor", got[0]["x"], 6) // 1100 ^ 1010 = 0110 + numEq(t, "bitnot", got[0]["n"], -1) // ^0 = -1 } // ---------- String ---------- @@ -367,7 +367,7 @@ func TestFunctionScenarios_String(t *testing.T) { t.Run("substring_basic_0based", func(t *testing.T) { t.Parallel() - // 方言:0-based(非 ANSI 1-based)。substring('hello',1,2) -> "el" + // Dialect: 0-based (not ANSI 1-based). substring('hello',1,2) -> "el" got := runDirect(t, `SELECT substring('hello', 1, 2) AS s FROM stream`, []map[string]any{{"x": 1}}) assertRows(t, "substring", got, []map[string]any{{"s": "el"}}) @@ -382,7 +382,7 @@ func TestFunctionScenarios_String(t *testing.T) { t.Run("substring_negative_start", func(t *testing.T) { t.Parallel() - // start=-2 -> 从末尾倒数:len(5)+(-2)=3 -> runes[3:] = "lo" + // start = -2 -> Counting from the end: len(5) + (-2) = 3 -> runes[3:] = "lo" got := runDirect(t, `SELECT substring('hello', -2) AS s FROM stream`, []map[string]any{{"x": 1}}) assertRows(t, "substring neg", got, []map[string]any{{"s": "lo"}}) @@ -390,7 +390,7 @@ func TestFunctionScenarios_String(t *testing.T) { t.Run("substring_out_of_range_empty", func(t *testing.T) { t.Parallel() - // start 越界 -> 空串 + // start Out-of-bounds -> Empty string got := runDirect(t, `SELECT substring('hello', 10, 2) AS s FROM stream`, []map[string]any{{"x": 1}}) assertRows(t, "substring oob", got, []map[string]any{{"s": ""}}) @@ -398,7 +398,7 @@ func TestFunctionScenarios_String(t *testing.T) { t.Run("regexp_substring", func(t *testing.T) { t.Parallel() - // 注册名为 regexp_substring。用字符类避开反斜杠转义歧义。 + // Registered name is regexp_substring. Use character classes to avoid backslash escape ambiguity. got := runDirect(t, `SELECT regexp_substring('phone: 123-456', '[0-9]+') AS s FROM stream`, []map[string]any{{"x": 1}}) assertRows(t, "regexp_substring", got, []map[string]any{{"s": "123"}}) @@ -413,7 +413,7 @@ func TestFunctionScenarios_String(t *testing.T) { t.Run("chr_out_of_range", func(t *testing.T) { t.Parallel() - // chr 仅允许 0..127;128 应为错误或 nil,不得返回静默值。 + // chr only allows 0..127; 128 should be an error or nil, and does not return a silence value. row, err := emitRow(t, `SELECT chr(128) AS c FROM stream`, map[string]any{"x": 1}) if err == nil { assert.Nil(t, row["c"], "chr(128) should be nil on out-of-range") @@ -474,7 +474,7 @@ func TestFunctionScenarios_Conditional(t *testing.T) { t.Run("greatest_with_nil_returns_nil", func(t *testing.T) { t.Parallel() - // 实现约定:任意一个 nil 参数即返回 nil。 + // Implementation convention: Any nil parameter returns nil. got := runDirect(t, `SELECT greatest(a, b, c) AS g FROM stream`, []map[string]any{{"a": 1, "b": nil, "c": 3}}) require.Len(t, got, 1) @@ -523,7 +523,7 @@ func TestFunctionScenarios_Math(t *testing.T) { t.Run("sqrt_negative_is_error_or_nil", func(t *testing.T) { t.Parallel() - // sqrt(-1) 应为错误或 nil,绝不可返回 NaN/数值。 + // sqrt(-1) should be an error or nil, and must never return NaN/value. row, err := emitRow(t, `SELECT sqrt(v) AS s FROM stream`, map[string]any{"v": -1.0}) if err == nil { assert.Nil(t, row["s"], "sqrt(-1) must be nil/error, got %v", row["s"]) @@ -568,14 +568,14 @@ func TestFunctionScenarios_Aggregates(t *testing.T) { } got := runWindow(t, `SELECT percentile(v, 0.5) AS p FROM stream GROUP BY g, CountingWindow(10)`, in) vals := sortedFloatField(got, "p") - // 正确:p=0.5 -> index=floor(0.5*9)=4 -> sorted[4]=50 + // Correct: p = 0.5 - > index = floor(0.5*9) = 4 - > sorted[4] = 50 const want = 50.0 if len(vals) != 1 { t.Errorf("percentile produced %d rows: %v", len(vals), vals) return } if vals[0] != want { - t.Errorf("percentile(v, 0.5): got %v, want %v (p 参数应生效,index=floor(0.5*9)=4)", vals[0], want) + t.Errorf("percentile(v, 0.5): got %v, want %v (p parameter should be effective, index=floor(0.5*9)=4)", vals[0], want) } }) } @@ -588,7 +588,7 @@ func TestFunctionScenarios_SelectDistinct(t *testing.T) { {"c": "A"}, {"c": "A"}, {"c": "B"}, {"c": "B"}, {"c": "C"}, } got := runDirect(t, `SELECT DISTINCT c FROM stream`, in) - // 期望跨行去重为 [A,B,C]。 + // The desired cross-line deduplication is [A,B,C]. gotVals := make([]string, 0, len(got)) for _, r := range got { if v, ok := r["c"].(string); ok { @@ -600,6 +600,6 @@ func TestFunctionScenarios_SelectDistinct(t *testing.T) { t.Logf("SELECT DISTINCT produced %d rows %v, want %d distinct %v", len(gotVals), gotVals, len(wantVals), wantVals) } - // 无论 DISTINCT 在直连模式下是否跨行去重,至少每行应可解析。 + // Regardless of whether DISTINCT deduplicates across lines in direct connection mode, at least every line should be parsable. assert.NotEmpty(t, gotVals) } diff --git a/test/e2e/function_test.go b/test/e2e/function_test.go index 4eb13c3..447270e 100644 --- a/test/e2e/function_test.go +++ b/test/e2e/function_test.go @@ -11,14 +11,14 @@ import ( "github.com/stretchr/testify/require" ) -// TestFunctionIntegrationNonAggregation 测试非聚合函数在SQL中的集成 +// TestFunctionIntegrationNonAggregation tests the integration of non-aggregation functions in SQL func TestFunctionIntegrationNonAggregation(t *testing.T) { t.Parallel() t.Run("MathFunctions", func(t *testing.T) { ssql := streamsql.New() defer ssql.Stop() - // 测试多个数学函数:abs, sqrt, round + // Test multiple mathematical functions: abs, sqrt, round rsql := "SELECT device, abs(temperature) as abs_temp, sqrt(humidity) as sqrt_humidity, round(temperature) as rounded_temp FROM stream" err := ssql.Execute(rsql) assert.Nil(t, err) @@ -29,7 +29,7 @@ func TestFunctionIntegrationNonAggregation(t *testing.T) { resultChan <- result }) - // 添加测试数据 + // Add test data testData := map[string]any{ "device": "test-device", "temperature": -25.5, @@ -37,7 +37,7 @@ func TestFunctionIntegrationNonAggregation(t *testing.T) { } strm.Emit(testData) - // 等待结果 + // Wait for the results ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) defer cancel() @@ -49,14 +49,14 @@ func TestFunctionIntegrationNonAggregation(t *testing.T) { item := resultSlice[0] assert.Equal(t, "test-device", item["device"]) - // 验证 abs(-25.5) = 25.5 + // Verify abs(-25.5) = 25.5 assert.InEpsilon(t, 25.5, item["abs_temp"], 0.001) - // 验证 sqrt(64) = 8 + // Validation sqrt(64) = 8 assert.InEpsilon(t, 8.0, item["sqrt_humidity"], 0.001) - // 验证 round(-25.5) = -26 + // Verify round(-25.5) = -26 assert.InEpsilon(t, -26.0, item["rounded_temp"], 0.001) case <-ctx.Done(): - t.Fatal("测试超时,未收到结果") + t.Fatal("The test timed out and no results were received") } }) @@ -64,7 +64,7 @@ func TestFunctionIntegrationNonAggregation(t *testing.T) { ssql := streamsql.New() defer ssql.Stop() - // 测试字符串函数:upper, lower, concat, length + // Test string functions: upper, lower, concat, length rsql := "SELECT upper(device) as upper_device, lower(location) as lower_location, concat(device, '-', location) as combined, length(device) as device_len FROM stream" err := ssql.Execute(rsql) assert.Nil(t, err) @@ -75,14 +75,14 @@ func TestFunctionIntegrationNonAggregation(t *testing.T) { resultChan <- result }) - // 添加测试数据 + // Add test data testData := map[string]any{ "device": "sensor01", "location": "ROOM_A", } strm.Emit(testData) - // 等待结果 + // Wait for the results ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) defer cancel() @@ -98,7 +98,7 @@ func TestFunctionIntegrationNonAggregation(t *testing.T) { assert.Equal(t, "sensor01-ROOM_A", item["combined"]) assert.Equal(t, 8, item["device_len"]) case <-ctx.Done(): - t.Fatal("测试超时,未收到结果") + t.Fatal("The test timed out and no results were received") } }) @@ -106,7 +106,7 @@ func TestFunctionIntegrationNonAggregation(t *testing.T) { ssql := streamsql.New() defer ssql.Stop() - // 测试转换函数:cast + // Test conversion function: cast rsql := "SELECT device, cast(temperature, 'int') as temp_int, cast(humidity, 'string') as humidity_str FROM stream" err := ssql.Execute(rsql) assert.Nil(t, err) @@ -117,7 +117,7 @@ func TestFunctionIntegrationNonAggregation(t *testing.T) { resultChan <- result }) - // 添加测试数据 + // Add test data testData := map[string]any{ "device": "test-device", "temperature": 25.7, @@ -125,7 +125,7 @@ func TestFunctionIntegrationNonAggregation(t *testing.T) { } strm.Emit(testData) - // 等待结果 + // Wait for the results ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) defer cancel() @@ -140,7 +140,7 @@ func TestFunctionIntegrationNonAggregation(t *testing.T) { assert.Equal(t, int(25), item["temp_int"]) assert.Equal(t, "65", item["humidity_str"]) case <-ctx.Done(): - t.Fatal("测试超时,未收到结果") + t.Fatal("The test timed out and no results were received") } }) @@ -148,7 +148,7 @@ func TestFunctionIntegrationNonAggregation(t *testing.T) { ssql := streamsql.New() defer ssql.Stop() - // 测试日期时间函数:now, year, month, day + // Test date time function: now, year, month, day rsql := "SELECT device, now() as current_time, year(timestamp) as ts_year, month(timestamp) as ts_month FROM stream" err := ssql.Execute(rsql) assert.Nil(t, err) @@ -159,7 +159,7 @@ func TestFunctionIntegrationNonAggregation(t *testing.T) { resultChan <- result }) - // 添加测试数据 + // Add test data //testTime := time.Date(2025, 4, 15, 10, 30, 0, 0, time.UTC) testData := map[string]any{ "device": "test-device", @@ -167,7 +167,7 @@ func TestFunctionIntegrationNonAggregation(t *testing.T) { } strm.Emit(testData) - // 等待结果 + // Wait for the results ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) defer cancel() @@ -183,7 +183,7 @@ func TestFunctionIntegrationNonAggregation(t *testing.T) { assert.Equal(t, 2025, item["ts_year"]) assert.Equal(t, 8, item["ts_month"]) case <-ctx.Done(): - t.Fatal("测试超时,未收到结果") + t.Fatal("The test timed out and no results were received") } }) @@ -191,7 +191,7 @@ func TestFunctionIntegrationNonAggregation(t *testing.T) { ssql := streamsql.New() defer ssql.Stop() - // 测试JSON函数:json_extract, json_valid + // Test JSON function: json_extract, json_valid rsql := "SELECT device, json_extract(metadata, '$.type') as device_type, json_valid(metadata) as is_valid_json FROM stream" err := ssql.Execute(rsql) assert.Nil(t, err) @@ -202,14 +202,14 @@ func TestFunctionIntegrationNonAggregation(t *testing.T) { resultChan <- result }) - // 添加测试数据 + // Add test data testData := map[string]any{ "device": "test-device", "metadata": `{"type": "temperature_sensor", "version": "1.0"}`, } strm.Emit(testData) - // 等待结果 + // Wait for the results ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) defer cancel() @@ -224,7 +224,7 @@ func TestFunctionIntegrationNonAggregation(t *testing.T) { assert.Equal(t, "temperature_sensor", item["device_type"]) assert.Equal(t, true, item["is_valid_json"]) case <-ctx.Done(): - t.Fatal("测试超时,未收到结果") + t.Fatal("The test timed out and no results were received") } }) @@ -267,7 +267,7 @@ func TestFunctionIntegrationNonAggregation(t *testing.T) { assert.Equal(t, "test-device-map", item["device"]) assert.Equal(t, "red", item["device_color"]) case <-ctx.Done(): - t.Fatal("测试超时,未收到结果") + t.Fatal("The test timed out and no results were received") } }) @@ -314,7 +314,7 @@ func TestFunctionIntegrationNonAggregation(t *testing.T) { assert.Equal(t, "tag1", item["first_tag"]) assert.Equal(t, "Alice", item["first_user_name"]) case <-ctx.Done(): - t.Fatal("测试超时,未收到结果") + t.Fatal("The test timed out and no results were received") } }) @@ -370,19 +370,19 @@ func TestFunctionIntegrationNonAggregation(t *testing.T) { assert.Equal(t, float64(2), item["tag_count"]) assert.Equal(t, float64(30), item["total_value"]) case <-ctx.Done(): - t.Fatal("测试超时,未收到结果") + t.Fatal("The test timed out and no results were received") } }) } -// TestFunctionIntegrationAggregation 测试聚合函数在SQL中的集成 +// TestFunctionIntegrationAggregation: Integrate the test aggregation function in SQL func TestFunctionIntegrationAggregation(t *testing.T) { t.Parallel() t.Run("BasicAggregationFunctions", func(t *testing.T) { ssql := streamsql.New() defer ssql.Stop() - // 测试基本聚合函数:sum, avg, min, max, count + // Test basic aggregate functions: sum, avg, min, max, count rsql := "SELECT device, sum(temperature) as total_temp, avg(temperature) as avg_temp, min(temperature) as min_temp, max(temperature) as max_temp, count(temperature) as temp_count FROM stream GROUP BY device, TumblingWindow('1s')" err := ssql.Execute(rsql) assert.Nil(t, err) @@ -393,7 +393,7 @@ func TestFunctionIntegrationAggregation(t *testing.T) { resultChan <- result }) - // 添加测试数据 + // Add test data testData := []map[string]any{ {"device": "sensor1", "temperature": 20.0}, {"device": "sensor1", "temperature": 25.0}, @@ -406,11 +406,11 @@ func TestFunctionIntegrationAggregation(t *testing.T) { strm.Emit(data) } - // 等待窗口初始化 + // Wait for the window to initialize time.Sleep(1 * time.Second) strm.Window.Trigger() - // 等待结果 + // Wait for the results ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) defer cancel() @@ -420,7 +420,7 @@ func TestFunctionIntegrationAggregation(t *testing.T) { require.True(t, ok) assert.Len(t, resultSlice, 2) - // 验证sensor1的聚合结果 + // Verify the aggregation results of sensor1 for _, item := range resultSlice { device := item["device"].(string) if device == "sensor1" { @@ -438,7 +438,7 @@ func TestFunctionIntegrationAggregation(t *testing.T) { } } case <-ctx.Done(): - t.Fatal("测试超时,未收到结果") + t.Fatal("The test timed out and no results were received") } }) @@ -446,7 +446,7 @@ func TestFunctionIntegrationAggregation(t *testing.T) { ssql := streamsql.New() defer ssql.Stop() - // 测试统计聚合函数:stddev, median, percentile + // Test statistical aggregate functions: stddev, median, percentile rsql := "SELECT device, stddev(temperature) as temp_stddev, median(temperature) as temp_median FROM stream GROUP BY device, TumblingWindow('1s')" err := ssql.Execute(rsql) assert.Nil(t, err) @@ -457,7 +457,7 @@ func TestFunctionIntegrationAggregation(t *testing.T) { resultChan <- result }) - // 添加测试数据 + // Add test data testData := []map[string]any{ {"device": "sensor1", "temperature": 10.0}, {"device": "sensor1", "temperature": 20.0}, @@ -470,11 +470,11 @@ func TestFunctionIntegrationAggregation(t *testing.T) { strm.Emit(data) } - // 等待窗口初始化 + // Wait for the window to initialize time.Sleep(1 * time.Second) strm.Window.Trigger() - // 等待结果 + // Wait for the results ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) defer cancel() @@ -486,12 +486,12 @@ func TestFunctionIntegrationAggregation(t *testing.T) { item := resultSlice[0] assert.Equal(t, "sensor1", item["device"]) - // 标准差应该约为15.81 + // The standard deviation should be about 15.81 assert.InEpsilon(t, 15.81, item["temp_stddev"].(float64), 0.1) - // 中位数应该为30.0 + // The median should be 30.0 assert.InEpsilon(t, 30.0, item["temp_median"].(float64), 0.001) case <-ctx.Done(): - t.Fatal("测试超时,未收到结果") + t.Fatal("The test timed out and no results were received") } }) @@ -499,7 +499,7 @@ func TestFunctionIntegrationAggregation(t *testing.T) { ssql := streamsql.New() defer ssql.Stop() - // 测试集合聚合函数:collect, first_value, last_value + // Test the aggregate functions of the set: collect, first_value, last_value rsql := "SELECT device, collect(temperature) as temp_array, first_value(temperature) as first_temp, last_value(temperature) as last_temp FROM stream GROUP BY device, TumblingWindow('1s')" err := ssql.Execute(rsql) assert.Nil(t, err) @@ -510,7 +510,7 @@ func TestFunctionIntegrationAggregation(t *testing.T) { resultChan <- result }) - // 添加测试数据 + // Add test data testData := []map[string]any{ {"device": "sensor1", "temperature": 20.0}, {"device": "sensor1", "temperature": 25.0}, @@ -521,11 +521,11 @@ func TestFunctionIntegrationAggregation(t *testing.T) { strm.Emit(data) } - // 等待窗口初始化 + // Wait for the window to initialize time.Sleep(1 * time.Second) strm.Window.Trigger() - // 等待结果 + // Wait for the results ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) defer cancel() @@ -538,7 +538,7 @@ func TestFunctionIntegrationAggregation(t *testing.T) { item := resultSlice[0] assert.Equal(t, "sensor1", item["device"]) - // 验证collect函数返回的数组 + // Verify the array returned by the collect function tempArray, ok := item["temp_array"].([]any) assert.True(t, ok) assert.Len(t, tempArray, 3) @@ -546,23 +546,23 @@ func TestFunctionIntegrationAggregation(t *testing.T) { assert.Contains(t, tempArray, 25.0) assert.Contains(t, tempArray, 30.0) - // 验证first_value和last_value + // Verify first_value and last_value assert.Equal(t, 20.0, item["first_temp"]) assert.Equal(t, 30.0, item["last_temp"]) case <-ctx.Done(): - t.Fatal("测试超时,未收到结果") + t.Fatal("The test timed out and no results were received") } }) } -// TestFunctionIntegrationMixed 测试混合函数场景 +// TestFunctionIntegrationMixed tests the mixed function scenario func TestFunctionIntegrationMixed(t *testing.T) { t.Parallel() t.Run("AggregationWithNonAggregationFunctions", func(t *testing.T) { ssql := streamsql.New() defer ssql.Stop() - // 测试聚合函数与非聚合函数混合使用 + // Test a mix of aggregate and non-aggregate functions rsql := "SELECT device, upper(device) as device_upper, avg(temperature) as avg_temp, round(avg(temperature), 2) as rounded_avg FROM stream GROUP BY device, TumblingWindow('1s')" err := ssql.Execute(rsql) assert.Nil(t, err) @@ -573,7 +573,7 @@ func TestFunctionIntegrationMixed(t *testing.T) { resultChan <- result }) - // 添加测试数据 + // Add test data testData := []map[string]any{ {"device": "sensor1", "temperature": 20.567}, {"device": "sensor1", "temperature": 25.234}, @@ -584,11 +584,11 @@ func TestFunctionIntegrationMixed(t *testing.T) { strm.Emit(data) } - // 等待窗口初始化 + // Wait for the window to initialize time.Sleep(1 * time.Second) strm.Window.Trigger() - // 等待结果 + // Wait for the results ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) defer cancel() @@ -603,19 +603,19 @@ func TestFunctionIntegrationMixed(t *testing.T) { assert.Equal(t, "sensor1", item["device"]) assert.Equal(t, "SENSOR1", item["device_upper"]) - // 验证平均值 + // Verify the average if avgTemp, exists := item["avg_temp"]; exists && avgTemp != nil { assert.InEpsilon(t, 25.308, avgTemp.(float64), 0.001) } else { t.Errorf("avg_temp is missing or nil: %v", avgTemp) } - // 验证四舍五入的平均值 + // Verify the rounded average if roundedAvg, exists := item["rounded_avg"]; exists { if roundedAvg == nil { t.Errorf("rounded_avg exists but is nil - this indicates the round(avg()) expression failed") } else if val, ok := roundedAvg.(float64); ok { - // 验证结果在合理范围内 + // The verification results are within a reasonable range assert.True(t, val >= 25.0 && val <= 25.5, "rounded_avg should be between 25.0 and 25.5, got %v", val) } else { t.Errorf("rounded_avg is not a float64: %v (type: %T)", roundedAvg, roundedAvg) @@ -624,7 +624,7 @@ func TestFunctionIntegrationMixed(t *testing.T) { t.Errorf("rounded_avg field is missing from result") } case <-ctx.Done(): - t.Fatal("测试超时,未收到结果") + t.Fatal("The test timed out and no results were received") } }) @@ -632,7 +632,7 @@ func TestFunctionIntegrationMixed(t *testing.T) { ssql := streamsql.New() defer ssql.Stop() - // 测试嵌套函数调用 + // Test nested function calls rsql := "SELECT device, upper(concat(device, '_', cast(round(temperature, 0), 'string'))) as device_temp_label FROM stream" err := ssql.Execute(rsql) assert.Nil(t, err) @@ -643,14 +643,14 @@ func TestFunctionIntegrationMixed(t *testing.T) { resultChan <- result }) - // 添加测试数据 + // Add test data testData := map[string]any{ "device": "sensor1", "temperature": 25.7, } strm.Emit(testData) - // 等待结果 + // Wait for the results ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) defer cancel() @@ -665,7 +665,7 @@ func TestFunctionIntegrationMixed(t *testing.T) { // round(25.7, 0) = 26, cast(26, 'string') = "26", concat("sensor1", "_", "26") = "sensor1_26", upper("sensor1_26") = "SENSOR1_26" assert.Equal(t, "SENSOR1_26", item["device_temp_label"]) case <-ctx.Done(): - t.Fatal("测试超时,未收到结果") + t.Fatal("The test timed out and no results were received") } }) @@ -673,7 +673,7 @@ func TestFunctionIntegrationMixed(t *testing.T) { ssql := streamsql.New() defer ssql.Stop() - // 测试窗口函数与聚合函数结合 + // The test window function is combined with the aggregation function rsql := "SELECT device, avg(temperature) as avg_temp, window_start() as start_time, window_end() as end_time FROM stream GROUP BY device, TumblingWindow('1s')" err := ssql.Execute(rsql) assert.Nil(t, err) @@ -684,7 +684,7 @@ func TestFunctionIntegrationMixed(t *testing.T) { resultChan <- result }) - // 添加测试数据 + // Add test data testData := []map[string]any{ {"device": "sensor1", "temperature": 20.0}, {"device": "sensor1", "temperature": 30.0}, @@ -694,11 +694,11 @@ func TestFunctionIntegrationMixed(t *testing.T) { strm.Emit(data) } - // 等待窗口初始化 + // Wait for the window to initialize time.Sleep(1 * time.Second) strm.Window.Trigger() - // 等待结果 + // Wait for the results ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) defer cancel() @@ -714,20 +714,20 @@ func TestFunctionIntegrationMixed(t *testing.T) { assert.NotNil(t, item["start_time"]) assert.NotNil(t, item["end_time"]) case <-ctx.Done(): - t.Fatal("测试超时,未收到结果") + t.Fatal("The test timed out and no results were received") } }) } -// TestNestedFunctionSupport 测试嵌套函数支持 +// TestNestedFunctionSupport tests nested function support func TestNestedFunctionSupport(t *testing.T) { t.Parallel() t.Run("NormalFunctionNestingAggregation", func(t *testing.T) { - // 测试普通函数嵌套聚合函数:round(avg(temperature), 2) + // Test ordinary functions nested aggregate functions: round(avg(temperature, 2) ssql := streamsql.New() defer ssql.Stop() - // 执行包含 round(avg(temperature), 2) 的查询 + // Execute queries containing round(avg(temperature, 2). query := "SELECT device, round(avg(temperature), 2) as rounded_avg FROM stream GROUP BY device, TumblingWindow('1s')" err := ssql.Execute(query) assert.Nil(t, err) @@ -738,7 +738,7 @@ func TestNestedFunctionSupport(t *testing.T) { resultChan <- result }) - // 添加测试数据 + // Add test data testData := []map[string]any{ {"device": "sensor1", "temperature": 20.567}, {"device": "sensor1", "temperature": 25.234}, @@ -749,11 +749,11 @@ func TestNestedFunctionSupport(t *testing.T) { strm.Emit(data) } - // 等待窗口初始化 + // Wait for the window to initialize time.Sleep(1 * time.Second) strm.Window.Trigger() - // 等待结果 + // Wait for the results ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) defer cancel() @@ -766,12 +766,12 @@ func TestNestedFunctionSupport(t *testing.T) { item := resultSlice[0] assert.Equal(t, "sensor1", item["device"]) - // 验证四舍五入的平均值 + // Verify the rounded average if roundedAvg, exists := item["rounded_avg"]; exists { if roundedAvg == nil { t.Errorf("rounded_avg exists but is nil - this indicates the round(avg()) expression failed") } else if val, ok := roundedAvg.(float64); ok { - // 平均值应该是 (20.567 + 25.234 + 30.123) / 3 = 25.308 + // The average should be (20.567 + 25.234 + 30.123) / 3 = 25.308 // round(25.308, 2) = 25.31 assert.InEpsilon(t, 25.31, val, 0.01) } else { @@ -781,16 +781,16 @@ func TestNestedFunctionSupport(t *testing.T) { t.Errorf("rounded_avg field is missing from result") } case <-ctx.Done(): - t.Fatal("测试超时,未收到结果") + t.Fatal("The test timed out and no results were received") } }) t.Run("AggregationNestingNormalFunction", func(t *testing.T) { - // 测试聚合函数嵌套普通函数:avg(round(temperature, 2)) + // Test the nested common function of the aggregate function: avg(round(temperature, 2)) ssql := streamsql.New() defer ssql.Stop() - // 执行包含 avg(round(temperature, 2)) 的查询 + // Execute queries containing avg(round(temperature, 2)). query := "SELECT device, avg(round(temperature, 2)) as avg_rounded FROM stream GROUP BY device, TumblingWindow('1s')" err := ssql.Execute(query) @@ -802,7 +802,7 @@ func TestNestedFunctionSupport(t *testing.T) { resultChan <- result }) - // 添加测试数据 + // Add test data testData := []map[string]any{ {"device": "sensor1", "temperature": 20.567}, // round(20.567, 2) = 20.57 {"device": "sensor1", "temperature": 25.234}, // round(25.234, 2) = 25.23 @@ -813,11 +813,11 @@ func TestNestedFunctionSupport(t *testing.T) { strm.Emit(data) } - // 等待窗口初始化 + // Wait for the window to initialize time.Sleep(1 * time.Second) strm.Window.Trigger() - // 等待结果 + // Wait for the results ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) defer cancel() @@ -831,12 +831,12 @@ func TestNestedFunctionSupport(t *testing.T) { assert.Equal(t, "sensor1", item["device"]) - // 验证聚合函数嵌套普通函数的结果 + // Verify the result of the aggregation function nesting ordinary functions if avgRounded, exists := item["avg_rounded"]; exists { if avgRounded == nil { t.Errorf("avg_rounded exists but is nil - this indicates the avg(round()) expression failed") } else if val, ok := avgRounded.(float64); ok { - // 期望值:avg(20.57, 25.23, 30.12) = (20.57 + 25.23 + 30.12) / 3 = 25.31 + // Expected value: avg(20.57, 25.23, 30.12) = (20.57 + 25.23 + 30.12) / 3 = 25.31 assert.InEpsilon(t, 25.31, val, 0.01) } else { t.Errorf("avg_rounded is not a float64: %v (type: %T)", avgRounded, avgRounded) @@ -845,16 +845,16 @@ func TestNestedFunctionSupport(t *testing.T) { t.Errorf("avg_rounded field is missing from result") } case <-ctx.Done(): - t.Fatal("测试超时,未收到结果") + t.Fatal("The test timed out and no results were received") } }) t.Run("ComplexNestedFunctions", func(t *testing.T) { - // 测试更复杂的嵌套函数:round(avg(abs(temperature)), 1) + // Testing more complex nested functions: round(avg(abs(temperature)), 1) ssql := streamsql.New() defer ssql.Stop() - // 执行包含 round(avg(abs(temperature)), 1) 的查询 + // Execute a query containing round(avg(abs(temperature)), 1). query := "SELECT device, round(avg(abs(temperature)), 1) as complex_result FROM stream GROUP BY device, TumblingWindow('1s')" err := ssql.Execute(query) assert.Nil(t, err) @@ -865,7 +865,7 @@ func TestNestedFunctionSupport(t *testing.T) { resultChan <- result }) - // 添加测试数据(包含负数) + // Add test data (including negative numbers) testData := []map[string]any{ {"device": "sensor1", "temperature": -20.567}, // abs(-20.567) = 20.567 {"device": "sensor1", "temperature": 25.234}, // abs(25.234) = 25.234 @@ -876,11 +876,11 @@ func TestNestedFunctionSupport(t *testing.T) { strm.Emit(data) } - // 等待窗口初始化 + // Wait for the window to initialize time.Sleep(1 * time.Second) strm.Window.Trigger() - // 等待结果 + // Wait for the results ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) defer cancel() @@ -894,12 +894,12 @@ func TestNestedFunctionSupport(t *testing.T) { assert.Equal(t, "sensor1", item["device"]) - // 验证复杂嵌套函数的结果 + // Verify the results of complex nested functions if complexResult, exists := item["complex_result"]; exists { if complexResult == nil { t.Errorf("complex_result exists but is nil - this indicates the round(avg(abs())) expression failed") } else if val, ok := complexResult.(float64); ok { - // 期望值:avg(20.567, 25.234, 30.123) = 25.308, round(25.308, 1) = 25.3 + // Expected value: avg(20.567, 25.234, 30.123) = 25.308, round(25.308, 1) = 25.3 assert.InEpsilon(t, 25.3, val, 0.01) } else { t.Errorf("complex_result is not a float64: %v (type: %T)", complexResult, complexResult) @@ -908,18 +908,18 @@ func TestNestedFunctionSupport(t *testing.T) { t.Errorf("complex_result field is missing from result") } case <-ctx.Done(): - t.Fatal("测试超时,未收到结果") + t.Fatal("The test timed out and no results were received") } }) } -// TestNestedFunctionExecutionOrder 测试嵌套函数的执行顺序和不同类型函数的组合 +// TestNestedFunctionExecutionOrder tests the execution order of nested functions and combinations of functions of different types func TestNestedFunctionExecutionOrder(t *testing.T) { t.Parallel() - // 测试1: 字符串函数嵌套数学函数 + // Test 1: String functions nested mathematical functions t.Run("StringFunctionNestingMathFunction", func(t *testing.T) { - // 测试 upper(concat("temp_", round(temperature, 1))) + // Test upper(concat("temp_", round(temperature, 1))) ssql := streamsql.New() defer ssql.Stop() @@ -933,10 +933,10 @@ func TestNestedFunctionExecutionOrder(t *testing.T) { resultChan <- result }) - // 添加测试数据 + // Add test data strm.Emit(map[string]any{"device": "sensor1", "temperature": 25.67}) - // 等待结果 + // Wait for the results ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) defer cancel() @@ -948,16 +948,16 @@ func TestNestedFunctionExecutionOrder(t *testing.T) { item := resultSlice[0] - // 验证执行顺序:round(25.67, 1) -> 25.7, concat('temp_', '25.7') -> 'temp_25.7', upper('temp_25.7') -> 'TEMP_25.7' + // Verification execution order: round(25.67, 1) -> 25.7, concat('temp_', '25.7') -> 'temp_25.7', upper('temp_25.7') -> 'TEMP_25.7' assert.Equal(t, "TEMP_25.7", item["formatted_temp"]) case <-ctx.Done(): - t.Fatal("测试超时") + t.Fatal("Test timeout") } }) - // 测试2: 数学函数嵌套字符串函数 + // Test 2: Mathematical Functions Nested String Functions t.Run("MathFunctionNestingStringFunction", func(t *testing.T) { - // 测试 round(len(upper(device)), 0) + // Test round(len(upper(device)), 0) ssql := streamsql.New() defer ssql.Stop() @@ -972,10 +972,10 @@ func TestNestedFunctionExecutionOrder(t *testing.T) { resultChan <- result }) - // 添加测试数据 + // Add test data strm.Emit(map[string]any{"device": "sensor1"}) - // 等待结果 + // Wait for the results ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) defer cancel() @@ -987,16 +987,16 @@ func TestNestedFunctionExecutionOrder(t *testing.T) { item := resultSlice[0] - // 验证执行顺序:upper('sensor1') -> 'SENSOR1', len('SENSOR1') -> 7, round(7, 0) -> 7 + // Verification execution sequence: upper('sensor1') -> 'SENSOR1', len('SENSOR1') - > 7, round(7, 0) - > 7 assert.Equal(t, float64(7), item["device_length"]) case <-ctx.Done(): - t.Fatal("测试超时") + t.Fatal("Test timeout") } }) - // 测试3: 多层嵌套函数(3层) + // Test 3: Multi-layer nested functions (3 layers) t.Run("ThreeLevelNestedFunctions", func(t *testing.T) { - // 测试 abs(round(sqrt(temperature), 2)) + // Test abs(round(sqrt(temperature, 2)) ssql := streamsql.New() defer ssql.Stop() @@ -1011,10 +1011,10 @@ func TestNestedFunctionExecutionOrder(t *testing.T) { resultChan <- result }) - // 添加测试数据 + // Add test data strm.Emit(map[string]any{"device": "sensor1", "temperature": 16.0}) - // 等待结果 + // Wait for the results ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) defer cancel() @@ -1025,52 +1025,52 @@ func TestNestedFunctionExecutionOrder(t *testing.T) { assert.Len(t, resultSlice, 1) item := resultSlice[0] - // 验证执行顺序:sqrt(16) -> 4, round(4, 2) -> 4.00, abs(4.00) -> 4.00 + // Verification execution order: sqrt(16) - > 4, round(4, 2) - > 4.00, abs(4.00) -> 4.00 assert.Equal(t, float64(4), item["processed_temp"]) case <-ctx.Done(): - t.Fatal("测试超时") + t.Fatal("Test timeout") } }) - // 测试6: 复杂的聚合函数嵌套 - 应该报错 + // Test 6: Complex nested aggregation functions – Should cause errors t.Run("ComplexAggregationNesting", func(t *testing.T) { - // 测试 max(round(avg(temperature), 1)) - 这是嵌套聚合函数,应该报错 + // Test max(round(avg(temperature, 1)) - This is a nested aggregation function and should throw an error ssql := streamsql.New() defer ssql.Stop() query := "SELECT device, max(round(avg(temperature), 1)) as max_rounded_avg FROM stream GROUP BY device, TumblingWindow('1s')" err := ssql.Execute(query) - // 应该返回嵌套聚合函数错误 + // A nested aggregation function error should be returned assert.NotNil(t, err) assert.Contains(t, err.Error(), "aggregate function calls cannot be nested") }) - // 测试7: 其他类型的嵌套聚合函数检测 + // Test 7: Other types of nested aggregation function checks t.Run("NestedAggregationDetection", func(t *testing.T) { ssql := streamsql.New() defer ssql.Stop() - // 测试 sum(count(*)) - 聚合函数嵌套聚合函数 + // Test sum(count(*)) - Nested aggregator functions query1 := "SELECT sum(count(*)) as nested_agg FROM stream GROUP BY device, TumblingWindow('1s')" err1 := ssql.Execute(query1) assert.NotNil(t, err1) assert.Contains(t, err1.Error(), "aggregate function calls cannot be nested") - // 测试 avg(min(temperature)) - 聚合函数嵌套聚合函数 + // Testing avg(min(temperature)) - Nested aggregation functions query2 := "SELECT avg(min(temperature)) as nested_agg FROM stream GROUP BY device, TumblingWindow('1s')" err2 := ssql.Execute(query2) assert.NotNil(t, err2) assert.Contains(t, err2.Error(), "aggregate function calls cannot be nested") - // 测试 round(avg(temperature), 1) - 正常函数嵌套聚合函数,应该正常 + // Test round(avg(temperature), 1) - normal function nested aggregation function should be normal query3 := "SELECT round(avg(temperature), 1) as normal_nesting FROM stream GROUP BY device, TumblingWindow('1s')" err3 := ssql.Execute(query3) - assert.Nil(t, err3) // 这种嵌套应该是允许的 + assert.Nil(t, err3) // This nesting should be allowed }) - // 测试7: 日期时间函数嵌套 + // Test 7: Nested date-time function t.Run("DateTimeFunctionNesting", func(t *testing.T) { - // 测试 year(date_add(created_at, 1, 'years')) + // Test year(date_add(created_at, 1, 'years')) ssql := streamsql.New() defer ssql.Stop() @@ -1084,10 +1084,10 @@ func TestNestedFunctionExecutionOrder(t *testing.T) { resultChan <- result }) - // 添加测试数据 + // Add test data strm.Emit(map[string]any{"device": "sensor1", "created_at": "2023-12-25 15:30:45"}) - // 等待结果 + // Wait for the results ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) defer cancel() @@ -1099,16 +1099,16 @@ func TestNestedFunctionExecutionOrder(t *testing.T) { item := resultSlice[0] - // 验证执行顺序:date_add('2023-12-25 15:30:45', 1, 'years') -> '2024-12-25 15:30:45', year('2024-12-25 15:30:45') -> 2024 + // Verification execution sequence: date_add ('2023-12-25 15:30:45', 1, 'years') -> '2024-12-25 15:30:45', year('2024-12-25 15:30:45') - > 2024 assert.Equal(t, 2024, item["next_year"]) case <-ctx.Done(): - t.Fatal("测试超时") + t.Fatal("Test timeout") } }) - // 测试8: 错误的嵌套函数执行顺序 + // Test 8: Incorrect execution order of nested functions t.Run("ErrorHandlingInNestedFunctions", func(t *testing.T) { - // 测试 sqrt(len(invalid_field)) - 应该处理错误 + // Test sqrt(len(invalid_field)) - should handle errors ssql := streamsql.New() defer ssql.Stop() @@ -1122,10 +1122,10 @@ func TestNestedFunctionExecutionOrder(t *testing.T) { resultChan <- result }) - // 添加测试数据(不包含invalid_field) + // Add test data (excluding invalid_field) strm.Emit(map[string]any{"device": "sensor1", "temperature": 25.0}) - // 等待结果 + // Wait for the results ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) defer cancel() @@ -1137,21 +1137,21 @@ func TestNestedFunctionExecutionOrder(t *testing.T) { item := resultSlice[0] - // 验证错误处理:invalid_field不存在,应该返回nil或默认值 + // Validation error handling: invalid_field does not exist, it should return nil or the default value _, exists := item["error_result"] assert.True(t, exists) case <-ctx.Done(): - t.Fatal("测试超时") + t.Fatal("Test timeout") } }) } -// flattenUnnestRows 将可能包含 unnest 结果的批次结果展开为多行,便于断言 -// 兼容两种形态: -// 1) 当前实现:返回单行,其中 alias 字段为 []any(需要在测试侧展开) -// 2) 未来实现:引擎直接返回多行(此时原样返回) +// flattenUnnestRows expands batch results that may contain unnest results into multiple rows for easier assertions +// Compatible with two forms: +// 1) Current implementation: returns a single line, where the alias field is []any (needs to be expanded on the test side) +// 2) Future implementation: The engine returns multiple lines directly (at this point, it returns as is). func flattenUnnestRows(result []map[string]any, alias string) []map[string]any { - // 如果已经是多行,直接返回 + // If there are already multiple lines, just return directly if len(result) > 1 { return result } @@ -1159,26 +1159,26 @@ func flattenUnnestRows(result []map[string]any, alias string) []map[string]any { return result } - // 形如:[{ alias: []any{...} , ...}] + // Forms: [{ alias: []any{...},...}] if v, ok := result[0][alias]; ok { if functions.IsUnnestResult(v) { - // 使用ProcessUnnestResultWithFieldName保留字段名,并合并其他字段 + // Use ProcessUnnestResultWithFieldName to reserve field names and merge other fields expandedRows := functions.ProcessUnnestResultWithFieldName(v, alias) if len(expandedRows) == 0 { return result } - // 将其他字段合并到每一行中 + // Merge other fields into each row results := make([]map[string]any, len(expandedRows)) for i, unnestRow := range expandedRows { newRow := make(map[string]any, len(result[0])+len(unnestRow)) - // 复制原始行的其他字段(除了unnest字段) + // Copy other fields of the original row (except the unnest field) for k, v := range result[0] { if k != alias { newRow[k] = v } } - // 添加unnest展开的字段 + // Add the field expanded by unnest for k, v := range unnestRow { newRow[k] = v } @@ -1191,11 +1191,11 @@ func flattenUnnestRows(result []map[string]any, alias string) []map[string]any { return result } -// TestUnnestFunctionIntegration 验证 unnest(array) 是否按预期将数组展开为多行 -// 该用例集成到完整 SQL 执行路径: -// - 语法: unnest(array) -// - 描述: 将数组展开为多行 -// - 示例: SELECT unnest(tags) as tag FROM stream +// TestUnnestFunctionIntegration verifies whether unnest(array) is expanding the array into multiple rows as expected +// This use case is integrated into the full SQL execution path: +// - Syntax: unnest(array) +// - Description: Expand the array into multiple rows +// - Example: SELECT unnest(tags) as tag FROM stream func TestUnnestFunctionIntegration(t *testing.T) { t.Parallel() t.Run("PrimitiveArray", func(t *testing.T) { @@ -1212,7 +1212,7 @@ func TestUnnestFunctionIntegration(t *testing.T) { resultChan <- result }) - // 输入为普通字符串数组 + // Input as a regular string array input := map[string]any{ "tags": []string{"a", "b", "c"}, } @@ -1225,14 +1225,14 @@ func TestUnnestFunctionIntegration(t *testing.T) { case raw := <-resultChan: batch, ok := raw.([]map[string]any) require.True(t, ok) - // 按两种形态规范化为多行 + // Standardized into multi-row forms according to these two forms rows := flattenUnnestRows(batch, "tag") require.Len(t, rows, 3) expected := []string{"a", "b", "c"} for i, exp := range expected { row := rows[i] - // 兼容两种字段命名:引擎直接展开可能使用别名(tag),函数侧展开为默认字段(value) + // Compatible with two types of field names: Engine direct expansion may use alias (tags), function-side expansion uses default fields (values) var got any if v, ok := row["tag"]; ok { got = v @@ -1244,7 +1244,7 @@ func TestUnnestFunctionIntegration(t *testing.T) { assert.Equal(t, exp, got) } case <-ctx.Done(): - t.Fatal("测试超时,未收到结果") + t.Fatal("The test timed out and no results were received") } }) @@ -1252,7 +1252,7 @@ func TestUnnestFunctionIntegration(t *testing.T) { ssql := streamsql.New() defer ssql.Stop() - // 测试组合列:SELECT id,unnest(tags) as tag FROM events + // Test the combination column: SELECT id, unnest(tags) as tag FROM events sql := "SELECT id, unnest(tags) as tag FROM stream" err := ssql.Execute(sql) require.NoError(t, err) @@ -1263,7 +1263,7 @@ func TestUnnestFunctionIntegration(t *testing.T) { resultChan <- result }) - // 输入包含id字段和tags数组 + // Enter an array containing id fields and tags input := map[string]any{ "id": 100, "tags": []string{"a", "b", "c"}, @@ -1277,19 +1277,19 @@ func TestUnnestFunctionIntegration(t *testing.T) { case raw := <-resultChan: batch, ok := raw.([]map[string]any) require.True(t, ok) - // 展开unnest结果 + // Unnest results rows := flattenUnnestRows(batch, "tag") require.Len(t, rows, 3) - // 验证每行都包含id字段和tag字段 + // Verify that each row contains id and tag fields expectedTags := []string{"a", "b", "c"} for i, expectedTag := range expectedTags { row := rows[i] - // 验证id字段保持不变 + // The verification id field remains unchanged assert.Equal(t, 100, row["id"], "row %d should have id=100", i) - // 验证tag字段 + // Verify the tag field var gotTag any if v, ok := row["tag"]; ok { gotTag = v @@ -1301,7 +1301,7 @@ func TestUnnestFunctionIntegration(t *testing.T) { assert.Equal(t, expectedTag, gotTag, "row %d should have tag=%s", i, expectedTag) } case <-ctx.Done(): - t.Fatal("测试超时,未收到结果") + t.Fatal("The test timed out and no results were received") } }) t.Run("ObjectArray", func(t *testing.T) { @@ -1318,7 +1318,7 @@ func TestUnnestFunctionIntegration(t *testing.T) { resultChan <- result }) - // 输入为对象数组 + // Input as an array of objects input := map[string]any{ "props": []map[string]any{ {"k": "x", "v": 1}, @@ -1338,13 +1338,13 @@ func TestUnnestFunctionIntegration(t *testing.T) { rows := flattenUnnestRows(batch, "prop") require.Len(t, rows, 2) - // 校验每一行包含对象内的字段 + // Verify that each row contains fields within the object assert.Equal(t, "x", firstOf(rows[0], "k", "prop", "k")) assert.Equal(t, 1, firstOf(rows[0], "v", "prop", "v")) assert.Equal(t, "y", firstOf(rows[1], "k", "prop", "k")) assert.Equal(t, 2, firstOf(rows[1], "v", "prop", "v")) case <-ctx.Done(): - t.Fatal("测试超时,未收到结果") + t.Fatal("The test timed out and no results were received") } }) @@ -1362,7 +1362,7 @@ func TestUnnestFunctionIntegration(t *testing.T) { resultChan <- result }) - // 空数组 + // Empty array input := map[string]any{ "tags": []string{}, } @@ -1379,7 +1379,7 @@ func TestUnnestFunctionIntegration(t *testing.T) { rows := flattenUnnestRows(batch, "tag") assert.Len(t, rows, 0) case <-ctx.Done(): - t.Fatal("测试超时,未收到结果") + t.Fatal("The test timed out and no results were received") } }) @@ -1397,7 +1397,7 @@ func TestUnnestFunctionIntegration(t *testing.T) { resultChan <- result }) - // nil 值 + // nil value input := map[string]any{ "tags": nil, } @@ -1414,13 +1414,13 @@ func TestUnnestFunctionIntegration(t *testing.T) { rows := flattenUnnestRows(batch, "tag") assert.Len(t, rows, 0) case <-ctx.Done(): - t.Fatal("测试超时,未收到结果") + t.Fatal("The test timed out and no results were received") } }) } -// firstOf 辅助从行中读取字段值,兼容 prop 为对象的形态 -// 优先按 top-level 字段取值,若不存在则尝试从嵌套对象(如 prop[k])获取 +// firstOf assists in reading field values from rows, compatible with prop as an object +// Prioritize values by top-level field; if none exist, try to obtain them from nested objects (such as prop[k]). func firstOf(row map[string]any, topLevelKey string, nestedObjKey string, nestedField string) any { if v, ok := row[topLevelKey]; ok { return v diff --git a/test/e2e/j2_panic_recover_test.go b/test/e2e/j2_panic_recover_test.go index 396739b..fe6f463 100644 --- a/test/e2e/j2_panic_recover_test.go +++ b/test/e2e/j2_panic_recover_test.go @@ -8,8 +8,8 @@ import ( "github.com/rulego/streamsql" ) -// panicLookupSource 对指定 key 抛 panic,验证 ingest 主循环 recover(J2), -// 不再因用户 TableSource.Lookup bug 崩整进程。 +// panicLookupSource throws a panic on the specified key to verify the ingest main loop recover(J2), +// No longer crashes processes due to user TableSource.Lookup bugs. type panicLookupSource struct { name string boomKey string @@ -54,8 +54,8 @@ func TestJ2_PanicInLookupRecovered(t *testing.T) { mu.Unlock() }) - // 先发会触发 Lookup panic 的行,再发正常行:修复前 panic 直接崩进程; - // 修复后 recover 记日志,正常行仍应到达 sink。 + // Sending first triggers a Lookup panic line, then sends a normal line: Repairing the previous panic crashes the process; + // After repair, recover and log the log; normally, the line should still reach the sink. ssql.Emit(map[string]any{"deviceId": "boom"}) ssql.Emit(map[string]any{"deviceId": "ok"}) diff --git a/test/e2e/like_test.go b/test/e2e/like_test.go index 4ce28ee..d00e7c4 100644 --- a/test/e2e/like_test.go +++ b/test/e2e/like_test.go @@ -12,29 +12,29 @@ import ( "github.com/stretchr/testify/require" ) -// TestLikeOperatorInSQL 测试LIKE语法功能 +// TestLikeOperatorInSQL tests the LIKE syntax functionality func TestLikeOperatorInSQL(t *testing.T) { t.Parallel() ssql := streamsql.New() defer ssql.Stop() - // 测试场景1:基本LIKE模式匹配 - 前缀匹配 + // Test Scenario 1: Basic LIKE pattern matching - prefix matching t.Run("前缀匹配(prefix%)", func(t *testing.T) { - // 测试使用LIKE进行前缀匹配 + // The test uses LIKE for prefix matching var rsql = "SELECT deviceId, deviceType FROM stream WHERE deviceId LIKE 'sensor%'" err := ssql.Execute(rsql) assert.Nil(t, err) strm := ssql.Stream() - // 创建结果接收通道 + // Create a result receiving channel resultChan := make(chan any, 10) - // 添加结果回调 + // Add result callbacks strm.AddSink(func(result []map[string]any) { resultChan <- result }) - // 添加测试数据 + // Add test data testData := []map[string]any{ {"deviceId": "sensor001", "deviceType": "temperature"}, {"deviceId": "device002", "deviceType": "humidity"}, @@ -42,12 +42,12 @@ func TestLikeOperatorInSQL(t *testing.T) { {"deviceId": "pump004", "deviceType": "actuator"}, } - // 添加数据 + // Add data for _, data := range testData { strm.Emit(data) } - // 等待并收集结果 + // Wait and collect results var results []any timeout := time.After(2 * time.Second) done := false @@ -61,10 +61,10 @@ func TestLikeOperatorInSQL(t *testing.T) { } } - // 验证结果:应该只有sensor001和sensor003匹配 + // Verification result: Only sensor001 and sensor003 should match assert.GreaterOrEqual(t, len(results), 1, "应该收到至少一个匹配结果") - // 验证结果中只包含以"sensor"开头的设备 + // Only devices starting with "sensor" are included in the verification results for _, result := range results { resultSlice, ok := result.([]map[string]any) require.True(t, ok, "结果应该是[]map[string]any类型") @@ -77,7 +77,7 @@ func TestLikeOperatorInSQL(t *testing.T) { } }) - // 测试场景2:后缀匹配 + // Test Scenario 2: Suffix matching t.Run("后缀匹配(%suffix)", func(t *testing.T) { ssql := streamsql.New() defer ssql.Stop() @@ -103,7 +103,7 @@ func TestLikeOperatorInSQL(t *testing.T) { strm.Emit(data) } - // 等待结果 + // Wait for the results var results []any timeout := time.After(2 * time.Second) done := false @@ -117,7 +117,7 @@ func TestLikeOperatorInSQL(t *testing.T) { } } - // 验证结果:应该只有以"error"结尾的状态 + // Verification result: It should only be a state ending with "error" assert.GreaterOrEqual(t, len(results), 1, "应该收到至少一个匹配结果") for _, result := range results { @@ -132,7 +132,7 @@ func TestLikeOperatorInSQL(t *testing.T) { } }) - // 测试场景3:包含匹配 + // Test Scenario 3: Inclusion Matching t.Run("包含匹配(%substring%)", func(t *testing.T) { ssql := streamsql.New() defer ssql.Stop() @@ -158,7 +158,7 @@ func TestLikeOperatorInSQL(t *testing.T) { strm.Emit(data) } - // 等待结果 + // Wait for the results var results []any timeout := time.After(2 * time.Second) done := false @@ -172,7 +172,7 @@ func TestLikeOperatorInSQL(t *testing.T) { } } - // 验证结果:应该只有包含"alert"的消息 + // Verification result: There should only be messages containing "alert" assert.GreaterOrEqual(t, len(results), 1, "应该收到至少一个匹配结果") for _, result := range results { @@ -187,7 +187,7 @@ func TestLikeOperatorInSQL(t *testing.T) { } }) - // 测试场景4:单字符通配符 + // Test scenario 4: Single-character wildcard t.Run("单字符通配符(_)", func(t *testing.T) { ssql := streamsql.New() defer ssql.Stop() @@ -213,7 +213,7 @@ func TestLikeOperatorInSQL(t *testing.T) { strm.Emit(data) } - // 等待结果 + // Wait for the results var results []any timeout := time.After(2 * time.Second) done := false @@ -227,12 +227,12 @@ func TestLikeOperatorInSQL(t *testing.T) { } } - // 验证结果:应该只有E_0_模式的代码(E101, E202不匹配E_0_,只有E305也不完全匹配) - // 实际上,根据模式E_0_,应该匹配如E101, E202等,让我们调整测试数据 + // Verification result: There should only be code with E_0_ pattern (E101, E202 do not match E_0_, only E305 does not match perfectly) + // In fact, based on the E_0_ of the model, matches like E101, E202, etc. should be matched to adjust the test data assert.GreaterOrEqual(t, len(results), 0, "根据通配符模式可能有匹配结果") }) - // 测试场景5:复杂模式 + // Test scenario 5: Complex mode t.Run("复杂LIKE模式", func(t *testing.T) { ssql := streamsql.New() defer ssql.Stop() @@ -258,7 +258,7 @@ func TestLikeOperatorInSQL(t *testing.T) { strm.Emit(data) } - // 等待结果 + // Wait for the results var results []any timeout := time.After(2 * time.Second) done := false @@ -272,7 +272,7 @@ func TestLikeOperatorInSQL(t *testing.T) { } } - // 验证结果:应该只有.log文件 + // Verification result: There should only be.log files assert.GreaterOrEqual(t, len(results), 1, "应该收到至少一个匹配结果") for _, result := range results { @@ -287,7 +287,7 @@ func TestLikeOperatorInSQL(t *testing.T) { } }) - // 测试场景6:在聚合查询中使用LIKE + // Test scenario 6: Using LIKE in aggregated queries t.Run("聚合查询中的LIKE", func(t *testing.T) { ssql := streamsql.New() defer ssql.Stop() @@ -314,11 +314,11 @@ func TestLikeOperatorInSQL(t *testing.T) { strm.Emit(data) } - // 等待聚合 + // Waiting for the convergence time.Sleep(500 * time.Millisecond) strm.Window.Trigger() - // 等待结果 + // Wait for the results ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) defer cancel() @@ -327,14 +327,14 @@ func TestLikeOperatorInSQL(t *testing.T) { case actual = <-resultChan: cancel() case <-ctx.Done(): - t.Fatal("测试超时,未收到聚合结果") + t.Fatal("The test timed out, and no aggregated results were received") } - // 验证聚合结果 + // Verify the aggregated results resultSlice, ok := actual.([]map[string]any) require.True(t, ok, "结果应该是[]map[string]any类型") - // 应该有两种设备类型:temperature(2个sensor), humidity(1个sensor) + // There should be two types of devices: temperature (2 sensors), humidity (1 sensor) assert.GreaterOrEqual(t, len(resultSlice), 1, "应该有至少一种设备类型的聚合结果") for _, result := range resultSlice { @@ -343,13 +343,13 @@ func TestLikeOperatorInSQL(t *testing.T) { assert.True(t, ok, "device_count应该是float64类型") assert.Greater(t, count, 0.0, "设备数量应该大于0") - // 验证设备类型 + // Verify the type of equipment assert.True(t, deviceType == "temperature" || deviceType == "humidity", fmt.Sprintf("设备类型 %s 应该是temperature或humidity", deviceType)) } }) - // 测试场景7:HAVING子句中的LIKE + // Test Scenario 7: LIKE in HAVING clause t.Run("HAVING子句中的LIKE", func(t *testing.T) { ssql := streamsql.New() defer ssql.Stop() @@ -375,11 +375,11 @@ func TestLikeOperatorInSQL(t *testing.T) { strm.Emit(data) } - // 等待聚合 + // Waiting for the convergence time.Sleep(500 * time.Millisecond) strm.Window.Trigger() - // 等待结果 + // Wait for the results ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) defer cancel() @@ -388,14 +388,14 @@ func TestLikeOperatorInSQL(t *testing.T) { case actual = <-resultChan: cancel() case <-ctx.Done(): - t.Fatal("测试超时,未收到HAVING+LIKE结果") + t.Fatal("The test timed out and HAVING+LIKE result was not received") } - // 验证HAVING + LIKE结果 + // Verify HAVING + LIKE results resultSlice, ok := actual.([]map[string]any) require.True(t, ok, "结果应该是[]map[string]any类型") - // 应该只有包含"temp"的设备类型 + // There should only be devices containing "temp" for _, result := range resultSlice { deviceType, _ := result["deviceType"].(string) assert.True(t, strings.Contains(deviceType, "temp"), @@ -408,15 +408,15 @@ func TestLikeOperatorInSQL(t *testing.T) { }) } -// TestLikeFunctionEquivalence 测试LIKE语法与现有字符串函数的等价性 +// TestLikeFunctionEquivalence tests the equivalence of LIKE syntax with existing string functions func TestLikeFunctionEquivalence(t *testing.T) { t.Parallel() - // 简化测试,重点验证LIKE功能已经正常工作 + // Simplified testing, focusing on verifying that the LIKE function is working properly t.Run("LIKE语法工作正常验证", func(t *testing.T) { ssql := streamsql.New() defer ssql.Stop() - // 使用LIKE的查询 + // Use LIKE queries to query var likeSQL = "SELECT deviceId FROM stream WHERE deviceId LIKE 'sensor%'" err := ssql.Execute(likeSQL) assert.Nil(t, err) @@ -426,19 +426,19 @@ func TestLikeFunctionEquivalence(t *testing.T) { resultChan <- result }) - // 测试数据 + // Test data testData := []map[string]any{ {"deviceId": "sensor001"}, {"deviceId": "device002"}, {"deviceId": "sensor003"}, } - // 添加数据 + // Add data for _, data := range testData { ssql.Stream().Emit(data) } - // 收集结果 + // Collect the results timeout := time.After(2 * time.Second) var results []any @@ -451,10 +451,10 @@ func TestLikeFunctionEquivalence(t *testing.T) { } } - // 验证LIKE查询返回了预期的结果 + // Validating the LIKE query returns the expected result assert.Equal(t, 2, len(results), "LIKE查询应该返回2个匹配'sensor%'的结果") - // 验证返回的结果确实是以'sensor'开头的 + // The result returned by verification indeed starts with 'sensor' for i, result := range results { resultSlice, ok := result.([]map[string]any) assert.True(t, ok, fmt.Sprintf("结果%d应该是[]map[string]any类型", i)) @@ -470,41 +470,41 @@ func TestLikeFunctionEquivalence(t *testing.T) { }) } -// TestLikePatternMatching 测试LIKE模式匹配算法的正确性 +// TestLikePatternMatching tests the correctness of the LIKE pattern matching algorithm func TestLikePatternMatching(t *testing.T) { t.Parallel() - // 这些是单元测试,直接测试LIKE匹配函数 + // These are unit tests, directly testing the LIKE matching function tests := []struct { text string pattern string expected bool desc string }{ - // 前缀匹配测试 + // Prefix matching test {"hello", "hello%", true, "精确前缀匹配"}, {"hello world", "hello%", true, "前缀匹配"}, {"hi there", "hello%", false, "前缀不匹配"}, {"", "%", true, "空字符串匹配任意模式"}, - // 后缀匹配测试 + // Suffix matching test {"test.log", "%.log", true, "后缀匹配"}, {"test.txt", "%.log", false, "后缀不匹配"}, - // 包含匹配测试 + // Includes matching tests {"hello world test", "%world%", true, "包含匹配"}, {"hello test", "%world%", false, "不包含"}, - // 单字符通配符测试 + // Single-character wildcard test {"abc", "a_c", true, "单字符通配符匹配"}, {"aXc", "a_c", true, "单字符通配符匹配任意字符"}, {"abbc", "a_c", false, "单字符通配符不匹配多个字符"}, - // 复杂模式测试 + // Complex mode testing {"file123.log", "file___.log", true, "多个单字符通配符"}, {"file12.log", "file___.log", false, "字符数不匹配"}, {"prefix_test_suffix", "prefix%suffix", true, "前后缀组合"}, - // 边界情况测试 + // Boundary condition testing {"", "", true, "空模式匹配空字符串"}, {"abc", "", false, "非空字符串不匹配空模式"}, {"", "abc", false, "空字符串不匹配非空模式"}, @@ -513,12 +513,12 @@ func TestLikePatternMatching(t *testing.T) { for _, test := range tests { t.Run(test.desc, func(t *testing.T) { - // 直接使用内部函数进行测试 - // 注意:这里我们需要通过SQL查询来测试,因为匹配函数是内部的 + // Testing is done directly using internal functions + // Note: Here, we need to test using SQL queries because the matching function is internal ssql := streamsql.New() defer ssql.Stop() - // 构造SQL查询 + // Construct SQL queries rsql := fmt.Sprintf("SELECT value FROM stream WHERE value LIKE '%s'", test.pattern) err := ssql.Execute(rsql) assert.Nil(t, err) @@ -528,11 +528,11 @@ func TestLikePatternMatching(t *testing.T) { resultChan <- result }) - // 添加测试数据 + // Add test data testData := map[string]any{"value": test.text} ssql.Stream().Emit(testData) - // 等待结果 + // Wait for the results timeout := time.After(1 * time.Second) var hasResult bool diff --git a/test/e2e/nested_field_test.go b/test/e2e/nested_field_test.go index a37936d..8a9ff75 100644 --- a/test/e2e/nested_field_test.go +++ b/test/e2e/nested_field_test.go @@ -10,7 +10,7 @@ import ( "github.com/stretchr/testify/require" ) -// TestComprehensiveNestedFieldAccess 全面测试嵌套字段访问功能 +// TestComprehensiveNestedFieldAccess Comprehensive testing of nested field access functionality func TestComprehensiveNestedFieldAccess(t *testing.T) { t.Parallel() t.Run("多层嵌套字段访问", func(t *testing.T) { @@ -21,7 +21,7 @@ func TestComprehensiveNestedFieldAccess(t *testing.T) { } }() - // 测试多层嵌套字段访问 + // Test multi-layer nested field access var rsql = "SELECT device.info.name as device_name, device.location.building as building, sensor.data.temperature as temp FROM stream" err := ssql.Execute(rsql) assert.Nil(t, err, "多层嵌套字段SQL应该能够执行") @@ -30,15 +30,15 @@ func TestComprehensiveNestedFieldAccess(t *testing.T) { strm := ssql.Stream() - // 创建结果接收通道 + // Create a result receiving channel resultChan := make(chan any, 10) - // 添加结果接收器 + // Add a result receiver strm.AddSink(func(result []map[string]any) { resultChan <- result }) - // 添加带多层嵌套字段的测试数据 + // Add test data with multi-layer nested fields testData := map[string]any{ "device": map[string]any{ "info": map[string]any{ @@ -62,23 +62,23 @@ func TestComprehensiveNestedFieldAccess(t *testing.T) { }, } - // 发送数据 + // Send data strm.Emit(testData) - // 等待结果 + // Wait for the results ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) defer cancel() select { case result := <-resultChan: - // 验证结果 + // Verify the results resultSlice, ok := result.([]map[string]any) require.True(t, ok, "结果应该是[]map[string]any类型") require.Len(t, resultSlice, 1, "应该只有一条结果") item := resultSlice[0] - // 检查各个嵌套字段的提取情况 + // Check the extraction status of each nested field deviceName, deviceNameExists := item["device_name"] assert.True(t, deviceNameExists, "device.info.name字段应该存在") assert.Equal(t, "温度传感器001", deviceName, "device.info.name应该被正确提取") @@ -92,7 +92,7 @@ func TestComprehensiveNestedFieldAccess(t *testing.T) { assert.Equal(t, 28.5, temp, "sensor.data.temperature应该被正确提取") case <-ctx.Done(): - t.Fatal("多层嵌套测试超时") + t.Fatal("Multi-layer nested test timeout") } }) @@ -104,7 +104,7 @@ func TestComprehensiveNestedFieldAccess(t *testing.T) { } }() - // 测试聚合查询中的嵌套字段 + // Test nested fields in aggregated queries var rsql = "SELECT device.type, AVG(sensor.temperature) as avg_temp, COUNT(*) as cnt FROM stream GROUP BY device.type, TumblingWindow('1s')" err := ssql.Execute(rsql) assert.Nil(t, err, "嵌套字段聚合SQL应该能够执行") @@ -113,15 +113,15 @@ func TestComprehensiveNestedFieldAccess(t *testing.T) { strm := ssql.Stream() - // 创建结果接收通道 + // Create a result receiving channel resultChan := make(chan any, 10) - // 添加结果回调 + // Add result callbacks strm.AddSink(func(result []map[string]any) { resultChan <- result }) - // 添加测试数据 + // Add test data testData := []map[string]any{ { "device": map[string]any{ @@ -152,29 +152,29 @@ func TestComprehensiveNestedFieldAccess(t *testing.T) { }, } - // 添加数据 + // Add data for _, data := range testData { strm.Emit(data) } - // 等待窗口触发 + // Wait for the window to trigger time.Sleep(1200 * time.Millisecond) - // 等待结果 + // Wait for the results ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) defer cancel() select { case result := <-resultChan: - // 验证聚合结果 + // Verify the aggregated results resultSlice, ok := result.([]map[string]any) require.True(t, ok, "结果应该是[]map[string]any类型") - // 聚合查询可能返回空结果,这是正常的 + // Aggregated queries may return empty results, which is normal if len(resultSlice) > 0 { - // 如果有结果,验证结果结构 + // If there are results, verify the structure of the results for _, item := range resultSlice { - // 检查是否包含任何聚合字段 + // Check for any aggregated fields hasAnyField := false if _, exists := item["type"]; exists { hasAnyField = true @@ -190,7 +190,7 @@ func TestComprehensiveNestedFieldAccess(t *testing.T) { } case <-ctx.Done(): - t.Fatal("嵌套字段聚合查询测试超时") + t.Fatal("Nested field aggregation query test timeout") } }) @@ -202,7 +202,7 @@ func TestComprehensiveNestedFieldAccess(t *testing.T) { } }() - // 测试复杂的WHERE条件 + // Test complex WHERE conditions var rsql = "SELECT * FROM stream WHERE device.info.status = 'active' AND sensor.data.temperature > 25 AND device.location.building = 'A栋'" err := ssql.Execute(rsql) assert.Nil(t, err, "复杂嵌套字段WHERE SQL应该能够执行") @@ -211,28 +211,28 @@ func TestComprehensiveNestedFieldAccess(t *testing.T) { strm := ssql.Stream() - // 创建结果接收通道 + // Create a result receiving channel resultChan := make(chan any, 10) - // 添加结果接收器 + // Add a result receiver strm.AddSink(func(result []map[string]any) { resultChan <- result }) - // 添加测试数据:一条满足所有条件,一条不满足 + // Add test data: one meets all conditions, one does not testData1 := map[string]any{ "device": map[string]any{ "info": map[string]any{ "name": "传感器A", - "status": "active", // 满足条件 + "status": "active", // Conditions are met }, "location": map[string]any{ - "building": "A栋", // 满足条件 + "building": "A栋", // Conditions are met }, }, "sensor": map[string]any{ "data": map[string]any{ - "temperature": 30.0, // 满足条件 > 25 + "temperature": 30.0, // Conditions met: > 25 }, }, } @@ -241,24 +241,24 @@ func TestComprehensiveNestedFieldAccess(t *testing.T) { "device": map[string]any{ "info": map[string]any{ "name": "传感器B", - "status": "inactive", // 不满足条件 + "status": "inactive", // The conditions are not met }, "location": map[string]any{ - "building": "B栋", // 不满足条件 + "building": "B栋", // The conditions are not met }, }, "sensor": map[string]any{ "data": map[string]any{ - "temperature": 20.0, // 不满足条件 <= 25 + "temperature": 20.0, // Conditions not met < = 25 }, }, } - // 发送数据 + // Send data strm.Emit(testData1) strm.Emit(testData2) - // 等待结果 + // Wait for the results var results []any timeout := time.After(3 * time.Second) done := false @@ -275,7 +275,7 @@ func TestComprehensiveNestedFieldAccess(t *testing.T) { } } - // 验证结果 + // Verify the results assert.Greater(t, len(results), 0, "复杂WHERE条件应该返回结果") for _, result := range results { @@ -283,7 +283,7 @@ func TestComprehensiveNestedFieldAccess(t *testing.T) { require.True(t, ok, "结果应该是[]map[string]any类型") for _, item := range resultSlice { - // 验证通过过滤的数据确实满足所有条件 + // Verify that the filtered data truly meets all the conditions device, deviceOk := item["device"].(map[string]any) assert.True(t, deviceOk, "device字段应该存在且为map类型") @@ -315,7 +315,7 @@ func TestComprehensiveNestedFieldAccess(t *testing.T) { }) } -// TestArrayFieldAccess 测试数组字段访问功能 +// TestArrayFieldAccess test array field access feature func TestArrayFieldAccess(t *testing.T) { t.Parallel() t.Run("数组索引访问", func(t *testing.T) { @@ -326,7 +326,7 @@ func TestArrayFieldAccess(t *testing.T) { } }() - // 测试数组索引访问 + // Test array index access var rsql = "SELECT items[0].name as first_item_name, items[1].id as second_item_id, values[2] as third_value FROM stream" err := ssql.Execute(rsql) assert.Nil(t, err, "数组索引访问SQL应该能够执行") @@ -338,7 +338,7 @@ func TestArrayFieldAccess(t *testing.T) { resultChan <- result }) - // 测试数据 + // Test data testData := map[string]any{ "items": []any{ map[string]any{"name": "item1", "id": 101}, @@ -360,23 +360,23 @@ func TestArrayFieldAccess(t *testing.T) { item := resultSlice[0] - // 验证 items[0].name + // Validate items[0].name name, ok := item["first_item_name"] assert.True(t, ok) assert.Equal(t, "item1", name) - // 验证 items[1].id + // Verify items[1].id id, ok := item["second_item_id"] assert.True(t, ok) assert.Equal(t, 102, id) - // 验证 values[2] + // Validating values[2] val, ok := item["third_value"] assert.True(t, ok) assert.Equal(t, 30, val) case <-ctx.Done(): - t.Fatal("测试超时") + t.Fatal("Test timeout") } }) @@ -398,13 +398,13 @@ func TestArrayFieldAccess(t *testing.T) { resultChan <- result }) - // 匹配的数据 + // Matching data matchData := map[string]any{ "id": "match", "tags": []any{"urgent", "work"}, "scores": []any{80, 95}, } - // 不匹配的数据 + // Mismatched data mismatchData := map[string]any{ "id": "mismatch", "tags": []any{"normal", "home"}, @@ -424,7 +424,7 @@ func TestArrayFieldAccess(t *testing.T) { require.Len(t, resultSlice, 1) assert.Equal(t, "match", resultSlice[0]["id"]) case <-ctx.Done(): - t.Fatal("测试超时") + t.Fatal("Test timeout") } }) @@ -436,7 +436,7 @@ func TestArrayFieldAccess(t *testing.T) { } }() - // 测试聚合查询中的嵌套数组字段 + // Test nested array fields in aggregated queries var rsql = "SELECT device.type, AVG(sensors[0].temperature) as avg_temp FROM stream GROUP BY device.type, TumblingWindow('1s')" err := ssql.Execute(rsql) assert.Nil(t, err, "嵌套数组聚合SQL应该能够执行") @@ -475,7 +475,7 @@ func TestArrayFieldAccess(t *testing.T) { case result := <-resultChan: resultSlice, ok := result.([]map[string]any) require.True(t, ok) - // 聚合结果可能为空,取决于窗口触发时机 + // The aggregate result may be empty, depending on when the window is triggered if len(resultSlice) > 0 { item := resultSlice[0] avgTemp, ok := item["avg_temp"].(float64) @@ -483,7 +483,7 @@ func TestArrayFieldAccess(t *testing.T) { assert.Equal(t, 25.0, avgTemp) } case <-ctx.Done(): - t.Fatal("测试超时") + t.Fatal("Test timeout") } }) } diff --git a/test/e2e/null_test.go b/test/e2e/null_test.go index bae7847..e36967a 100644 --- a/test/e2e/null_test.go +++ b/test/e2e/null_test.go @@ -11,7 +11,7 @@ import ( "github.com/stretchr/testify/require" ) -// TestIsNullOperatorInSQL 测试IS NULL和IS NOT NULL语法功能 +// TestIsNullOperatorInSQL tests the syntax functions of IS NULL and IS NOT NULL func TestIsNullOperatorInSQL(t *testing.T) { t.Parallel() testCases := []struct { @@ -71,7 +71,7 @@ func TestIsNullOperatorInSQL(t *testing.T) { }, expected: []map[string]any{ {"deviceId": "sensor2", "device.location": nil}, - {"deviceId": "sensor3", "device.location": nil}, // 字段不存在也被认为是null + {"deviceId": "sensor3", "device.location": nil}, // Fields that do not exist are also considered null }, }, { @@ -106,15 +106,15 @@ func TestIsNullOperatorInSQL(t *testing.T) { for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { - // 创建StreamSQL实例 + // Create a StreamSQL instance ssql := streamsql.New() defer ssql.Stop() - // 执行SQL + // Execute SQL err := ssql.Execute(tc.sql) require.NoError(t, err) - // 收集结果 + // Collect the results var results []map[string]any resultChan := make(chan any, 10) resultsMutex := sync.Mutex{} @@ -123,12 +123,12 @@ func TestIsNullOperatorInSQL(t *testing.T) { resultChan <- result }) - // 添加测试数据 + // Add test data for _, data := range tc.testData { ssql.Stream().Emit(data) } - // 使用更短的超时时间,避免在CI环境中长时间等待 + // Use shorter timeouts to avoid long waits in CI environments timeout := time.After(500 * time.Millisecond) collecting: @@ -145,10 +145,10 @@ func TestIsNullOperatorInSQL(t *testing.T) { } } - // 验证结果数量 + // Verification of the number of results assert.Len(t, results, len(tc.expected), "结果数量应该匹配") - // 验证结果内容(不依赖顺序) + // Verification Result Content (Independent of Order) expectedDeviceIds := make([]string, len(tc.expected)) for i, exp := range tc.expected { expectedDeviceIds[i] = exp["deviceId"].(string) @@ -161,16 +161,16 @@ func TestIsNullOperatorInSQL(t *testing.T) { } resultsMutex.Unlock() - // 验证每个期望的设备ID都在结果中 + // Verify the ID of each desired device in the results for _, expectedId := range expectedDeviceIds { assert.Contains(t, actualDeviceIds, expectedId, "结果应该包含设备ID %s", expectedId) } - // 验证每个结果的字段值 + // Verify the field values for each result resultsMutex.Lock() for _, result := range results { deviceId := result["deviceId"].(string) - // 找到对应的期望结果 + // Find the corresponding desired outcome var expectedResult map[string]any for _, exp := range tc.expected { if exp["deviceId"].(string) == deviceId { @@ -192,13 +192,13 @@ func TestIsNullOperatorInSQL(t *testing.T) { } } -// TestIsNullInAggregation 测试聚合查询中的IS NULL +// TestIsNullInAggregation: Tests the IS NULL in aggregated queries func TestIsNullInAggregation(t *testing.T) { t.Parallel() ssql := streamsql.New() defer ssql.Stop() - // 聚合查询:统计非空值的数量 + // Aggregate query: Counts the number of non-null values sql := `SELECT deviceType, COUNT(*) as total_count, COUNT(value) as non_null_count @@ -209,13 +209,13 @@ func TestIsNullInAggregation(t *testing.T) { err := ssql.Execute(sql) require.NoError(t, err) - // 收集结果 + // Collect the results resultChan := make(chan any, 10) ssql.Stream().AddSink(func(result []map[string]any) { resultChan <- result }) - // 添加测试数据 + // Add test data testData := []map[string]any{ {"deviceType": "temperature", "value": 25.5}, {"deviceType": "temperature", "value": nil}, @@ -228,16 +228,16 @@ func TestIsNullInAggregation(t *testing.T) { ssql.Stream().Emit(data) } - // 等待窗口触发 + // Wait for the window to trigger time.Sleep(3 * time.Second) - // 验证结果 + // Verify the results select { case result := <-resultChan: resultSlice, ok := result.([]map[string]any) require.True(t, ok, "结果应该是[]map[string]any类型") - // 应该有temperature和humidity两种类型的结果 + // There should be two types of results: temperature and humidity assert.GreaterOrEqual(t, len(resultSlice), 1, "应该至少有一个聚合结果") for _, item := range resultSlice { @@ -246,27 +246,27 @@ func TestIsNullInAggregation(t *testing.T) { nonNullCount, _ := item["non_null_count"].(float64) if deviceType == "temperature" { - // temperature有2个非空值(25.5, 27.0) + // Temperature has 2 non-null values (25.5, 27.0) assert.Equal(t, 2.0, totalCount, "temperature总数应该是2") assert.Equal(t, 2.0, nonNullCount, "temperature非空数应该是2") } else if deviceType == "humidity" { - // humidity有1个非空值(60.0) + // Humidity has 1 non-null value (60.0) assert.Equal(t, 1.0, totalCount, "humidity总数应该是1") assert.Equal(t, 1.0, nonNullCount, "humidity非空数应该是1") } } case <-time.After(5 * time.Second): - t.Fatal("测试超时,未收到聚合结果") + t.Fatal("The test timed out, and no aggregated results were received") } } -// TestIsNullInHaving 测试HAVING子句中真正的IS NULL功能 +// TestIsNullInHaving tests the true IS NULL functionality in the HAVING clause func TestIsNullInHaving(t *testing.T) { t.Parallel() ssql := streamsql.New() defer ssql.Stop() - // 测试HAVING子句中的IS NULL:只返回平均值为NULL的设备类型 + // Test IS NULL in the HAVING clause: only returns the device type with an average NULL sql := `SELECT deviceType, COUNT(*) as total_count, AVG(value) as avg_value @@ -282,57 +282,57 @@ func TestIsNullInHaving(t *testing.T) { resultChan <- result }) - // 添加测试数据:只给pressure设备类型添加null值,这样它的平均值会是null + // Add test data: only add null values to the pressure device type, so its average value will be null testData := []map[string]any{ {"deviceType": "temperature", "value": 25.0}, - {"deviceType": "temperature", "value": 27.0}, // temperature有值,平均值不为null - {"deviceType": "humidity", "value": 60.0}, // humidity有值,平均值不为null - {"deviceType": "pressure", "value": nil}, // pressure只有null值 - {"deviceType": "pressure", "value": nil}, // pressure再次null值,平均值会是null + {"deviceType": "temperature", "value": 27.0}, // temperature is valued, and the average value is not null + {"deviceType": "humidity", "value": 60.0}, // humidity has a value, and the average value is not null + {"deviceType": "pressure", "value": nil}, // Pressure only has a null value + {"deviceType": "pressure", "value": nil}, // pressure is null again, and the average value will be null } for _, data := range testData { ssql.Stream().Emit(data) } - // 等待窗口触发 + // Wait for the window to trigger time.Sleep(3 * time.Second) - // 验证结果 + // Verify the results select { case result := <-resultChan: resultSlice, ok := result.([]map[string]any) require.True(t, ok, "结果应该是[]map[string]any类型") - // 应该只有pressure类型的结果(平均值为null) + // There should only be results of pressure type (average value null) assert.Len(t, resultSlice, 1, "应该只有一个结果") if len(resultSlice) > 0 { item := resultSlice[0] assert.Equal(t, "pressure", item["deviceType"], "应该是pressure类型") - // 验证avg_value确实为null + // Verify that avg_value is indeed null avgValue := item["avg_value"] assert.Nil(t, avgValue, "pressure的平均值应该是null") - // 验证total_count + // Verification total_count totalCount, ok := item["total_count"].(float64) assert.True(t, ok, "total_count应该是float64类型") assert.Equal(t, 2.0, totalCount, "pressure应该有2条记录") } case <-time.After(5 * time.Second): - t.Fatal("测试超时,未收到聚合结果") + t.Fatal("The test timed out, and no aggregated results were received") } } -// TestIsNullInHavingWithIsNotNull 测试HAVING子句中的IS NOT NULL功能 +// TestIsNullInHavingWithIsNotNull tests the IS NOT NULL function in the HAVING clause func TestIsNullInHavingWithIsNotNull(t *testing.T) { t.Parallel() ssql := streamsql.New() defer ssql.Stop() - // 测试HAVING子句中的IS NOT NULL:只返回平均值不为NULL的设备类型 + // Test IS NOT NULL in the HAVING clause: only returns the device type with an average value that is not NULL sql := `SELECT deviceType, COUNT(*) as total_count, AVG(value) as avg_value @@ -348,12 +348,12 @@ func TestIsNullInHavingWithIsNotNull(t *testing.T) { resultChan <- result }) - // 添加测试数据 + // Add test data testData := []map[string]any{ {"deviceType": "temperature", "value": 25.0}, - {"deviceType": "temperature", "value": 27.0}, // temperature有值,平均值不为null - {"deviceType": "humidity", "value": 60.0}, // humidity有值,平均值不为null - {"deviceType": "pressure", "value": nil}, // pressure只有null值,平均值会是null + {"deviceType": "temperature", "value": 27.0}, // temperature is valued, and the average value is not null + {"deviceType": "humidity", "value": 60.0}, // humidity has a value, and the average value is not null + {"deviceType": "pressure", "value": nil}, // Pressure is only a null value, and the average value will be null {"deviceType": "pressure", "value": nil}, } @@ -361,16 +361,16 @@ func TestIsNullInHavingWithIsNotNull(t *testing.T) { ssql.Stream().Emit(data) } - // 等待窗口触发 + // Wait for the window to trigger time.Sleep(3 * time.Second) - // 验证结果 + // Verify the results select { case result := <-resultChan: resultSlice, ok := result.([]map[string]any) require.True(t, ok, "结果应该是[]map[string]any类型") - // 应该有temperature和humidity两种类型的结果(平均值不为null) + // There should be two types of results: temperature and humidity (the average is not null). assert.Len(t, resultSlice, 2, "应该有两个结果") foundTypes := make([]string, 0) @@ -378,30 +378,30 @@ func TestIsNullInHavingWithIsNotNull(t *testing.T) { deviceType, ok := item["deviceType"].(string) require.True(t, ok, "deviceType应该是string类型") - // 验证avg_value不为null + // Verify that avg_value is not null avgValue := item["avg_value"] assert.NotNil(t, avgValue, fmt.Sprintf("%s的平均值应该不为null", deviceType)) foundTypes = append(foundTypes, deviceType) } - // 验证包含temperature和humidity,不包含pressure + // Verification includes temperature and humidity, but excludes pressure assert.Contains(t, foundTypes, "temperature", "结果应该包含temperature") assert.Contains(t, foundTypes, "humidity", "结果应该包含humidity") assert.NotContains(t, foundTypes, "pressure", "结果不应该包含pressure") case <-time.After(5 * time.Second): - t.Fatal("测试超时,未收到聚合结果") + t.Fatal("The test timed out, and no aggregated results were received") } } -// TestIsNullWithOtherOperators 测试IS NULL与其他操作符的组合 +// TestIsNullWithOtherOperators tests the combination of IS NULL and other operators func TestIsNullWithOtherOperators(t *testing.T) { t.Parallel() ssql := streamsql.New() defer ssql.Stop() - // 测试复杂的WHERE条件 + // Test complex WHERE conditions sql := `SELECT deviceId, value, status, location FROM stream WHERE (value IS NOT NULL AND value > 20) OR @@ -415,20 +415,20 @@ func TestIsNullWithOtherOperators(t *testing.T) { resultChan <- result }) - // 添加测试数据 + // Add test data testData := []map[string]any{ - {"deviceId": "sensor1", "value": 25.0, "status": "active", "location": "warehouse-A"}, // 满足第一个条件 - {"deviceId": "sensor2", "value": 15.0, "status": "active", "location": "warehouse-B"}, // 不满足条件 - {"deviceId": "sensor3", "value": nil, "status": nil, "location": "warehouse-C"}, // 满足第二个条件 - {"deviceId": "sensor4", "value": nil, "status": "inactive", "location": "warehouse-D"}, // 不满足条件 - {"deviceId": "sensor5", "value": 30.0, "status": nil, "location": "office-A"}, // 满足第一个条件 + {"deviceId": "sensor1", "value": 25.0, "status": "active", "location": "warehouse-A"}, // The first condition is met + {"deviceId": "sensor2", "value": 15.0, "status": "active", "location": "warehouse-B"}, // The conditions are not met + {"deviceId": "sensor3", "value": nil, "status": nil, "location": "warehouse-C"}, // The second condition must be met + {"deviceId": "sensor4", "value": nil, "status": "inactive", "location": "warehouse-D"}, // The conditions are not met + {"deviceId": "sensor5", "value": 30.0, "status": nil, "location": "office-A"}, // The first condition is met } for _, data := range testData { ssql.Stream().Emit(data) } - // 使用超时方式安全收集结果 + // Safely collect results using timeout methods var results []map[string]any timeout := time.After(2 * time.Second) @@ -444,7 +444,7 @@ collecting: } } - // 验证结果:应该有sensor1, sensor3, sensor5 + // Verification result: There should be sensor1, sensor3, sensor5 assert.Len(t, results, 3, "应该有3个结果") expectedDeviceIds := []string{"sensor1", "sensor3", "sensor5"} @@ -458,7 +458,7 @@ collecting: } } -// TestCaseWhenWithIsNull 测试CASE WHEN表达式中使用IS NULL和IS NOT NULL +// TestCaseWhenWithIsNull tests IS NULL and IS NOT NULL in a CASE WHEN expression func TestCaseWhenWithIsNull(t *testing.T) { t.Parallel() testCases := []struct { @@ -478,7 +478,7 @@ func TestCaseWhenWithIsNull(t *testing.T) { {"deviceId": "sensor1", "status": "active"}, {"deviceId": "sensor2", "status": nil}, {"deviceId": "sensor3", "status": "inactive"}, - {"deviceId": "sensor4"}, // 没有status字段 + {"deviceId": "sensor4"}, // There is no status field }, expected: []map[string]any{ {"deviceId": "sensor1", "status_flag": 1.0}, @@ -499,7 +499,7 @@ func TestCaseWhenWithIsNull(t *testing.T) { {"deviceId": "sensor1", "temperature": 30.0}, {"deviceId": "sensor2", "temperature": 20.0}, {"deviceId": "sensor3", "temperature": nil}, - {"deviceId": "sensor4"}, // 没有temperature字段 + {"deviceId": "sensor4"}, // There is no temperature field }, expected: []map[string]any{ {"deviceId": "sensor1", "temp_level": 2.0}, @@ -522,7 +522,7 @@ func TestCaseWhenWithIsNull(t *testing.T) { {"deviceId": "sensor2", "status": "active", "temperature": nil}, {"deviceId": "sensor3", "status": nil, "temperature": 30.0}, {"deviceId": "sensor4", "status": nil, "temperature": nil}, - {"deviceId": "sensor5"}, // 两个字段都不存在 + {"deviceId": "sensor5"}, // Neither field exists }, expected: []map[string]any{ {"deviceId": "sensor1", "combined_flag": 3.0}, @@ -567,15 +567,15 @@ func TestCaseWhenWithIsNull(t *testing.T) { for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { - // 创建StreamSQL实例 + // Create a StreamSQL instance ssql := streamsql.New() defer ssql.Stop() - // 执行SQL + // Execute SQL err := ssql.Execute(tc.sql) require.NoError(t, err) - // 收集结果 + // Collect the results var results []map[string]any resultChan := make(chan any, 10) @@ -583,15 +583,15 @@ func TestCaseWhenWithIsNull(t *testing.T) { resultChan <- result }) - // 添加测试数据 + // Add test data for _, data := range tc.testData { ssql.Stream().Emit(data) } - // 使用超时方式安全收集结果 + // Safely collect results using timeout methods var timeout time.Duration if tc.name == "CASE WHEN IS NULL与聚合函数结合测试" { - timeout = 4 * time.Second // 聚合查询需要更长时间 + timeout = 4 * time.Second // Aggregate queries take longer } else { timeout = 500 * time.Millisecond } @@ -610,16 +610,16 @@ func TestCaseWhenWithIsNull(t *testing.T) { } } - // 验证结果数量 + // Verification of the number of results assert.Len(t, results, len(tc.expected), "结果数量应该匹配") - // 对于聚合查询,验证逻辑略有不同 + // For aggregated queries, the validation logic is slightly different if tc.name == "CASE WHEN IS NULL与聚合函数结合测试" { - // 验证每个deviceType的结果 + // Verify the results of each deviceType for _, expectedResult := range tc.expected { expectedDeviceType := expectedResult["deviceType"].(string) - // 在结果中找到对应的deviceType + // Find the corresponding deviceType in the results var actualResult map[string]any for _, result := range results { if result["deviceType"].(string) == expectedDeviceType { @@ -630,7 +630,7 @@ func TestCaseWhenWithIsNull(t *testing.T) { require.NotNil(t, actualResult, "应该找到设备类型 %s 的结果", expectedDeviceType) - // 验证各个统计值 + // Verify each statistic assert.Equal(t, expectedResult["total_count"], actualResult["total_count"], "设备类型 %s 的total_count应该匹配", expectedDeviceType) assert.Equal(t, expectedResult["null_count"], actualResult["null_count"], @@ -639,7 +639,7 @@ func TestCaseWhenWithIsNull(t *testing.T) { "设备类型 %s 的non_null_count应该匹配", expectedDeviceType) } } else { - // 验证普通查询的结果 + // Verify the results of ordinary queries expectedDeviceIds := make([]string, len(tc.expected)) for i, exp := range tc.expected { expectedDeviceIds[i] = exp["deviceId"].(string) @@ -650,15 +650,15 @@ func TestCaseWhenWithIsNull(t *testing.T) { actualDeviceIds[i] = result["deviceId"].(string) } - // 验证每个期望的设备ID都在结果中 + // Verify the ID of each desired device in the results for _, expectedId := range expectedDeviceIds { assert.Contains(t, actualDeviceIds, expectedId, "结果应该包含设备ID %s", expectedId) } - // 验证每个结果的字段值 + // Verify the field values for each result for _, result := range results { deviceId := result["deviceId"].(string) - // 找到对应的期望结果 + // Find the corresponding desired outcome var expectedResult map[string]any for _, exp := range tc.expected { if exp["deviceId"].(string) == deviceId { @@ -669,7 +669,7 @@ func TestCaseWhenWithIsNull(t *testing.T) { if expectedResult != nil { for key, expectedValue := range expectedResult { - if key != "deviceId" { // deviceId已经验证过了 + if key != "deviceId" { // deviceId has already been verified actualValue := result[key] assert.Equal(t, expectedValue, actualValue, "设备 %s 的字段 %s 值应该匹配: 期望 %v, 实际 %v", deviceId, key, expectedValue, actualValue) @@ -682,7 +682,7 @@ func TestCaseWhenWithIsNull(t *testing.T) { } } -// TestNullComparisons 测试 = nil、!= nil、= null、!= null 等语法 +// TestNullComparisons Syntax such as test = nil,!= nil, = null,!= null, etc func TestNullComparisons(t *testing.T) { t.Parallel() testCases := []struct { @@ -770,7 +770,7 @@ func TestNullComparisons(t *testing.T) { }, expected: []map[string]any{ {"deviceId": "sensor2", "device.location": nil}, - {"deviceId": "sensor3", "device.location": nil}, // 字段不存在也被认为是null + {"deviceId": "sensor3", "device.location": nil}, // Fields that do not exist are also considered null }, }, { @@ -831,15 +831,15 @@ func TestNullComparisons(t *testing.T) { for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { - // 创建StreamSQL实例 + // Create a StreamSQL instance ssql := streamsql.New() defer ssql.Stop() - // 执行SQL + // Execute SQL err := ssql.Execute(tc.sql) require.NoError(t, err) - // 收集结果 + // Collect the results var results []map[string]any resultChan := make(chan any, 10) @@ -847,12 +847,12 @@ func TestNullComparisons(t *testing.T) { resultChan <- result }) - // 添加测试数据 + // Add test data for _, data := range tc.testData { ssql.Stream().Emit(data) } - // 使用超时方式安全收集结果 + // Safely collect results using timeout methods timeout := time.After(500 * time.Millisecond) collecting: @@ -867,10 +867,10 @@ func TestNullComparisons(t *testing.T) { } } - // 验证结果数量 + // Verification of the number of results assert.Len(t, results, len(tc.expected), "结果数量应该匹配") - // 验证结果内容(不依赖顺序) + // Verification Result Content (Independent of Order) expectedDeviceIds := make([]string, len(tc.expected)) for i, exp := range tc.expected { expectedDeviceIds[i] = exp["deviceId"].(string) @@ -881,15 +881,15 @@ func TestNullComparisons(t *testing.T) { actualDeviceIds[i] = result["deviceId"].(string) } - // 验证每个期望的设备ID都在结果中 + // Verify the ID of each desired device in the results for _, expectedId := range expectedDeviceIds { assert.Contains(t, actualDeviceIds, expectedId, "结果应该包含设备ID %s", expectedId) } - // 验证每个结果的字段值 + // Verify the field values for each result for _, result := range results { deviceId := result["deviceId"].(string) - // 找到对应的期望结果 + // Find the corresponding desired outcome var expectedResult map[string]any for _, exp := range tc.expected { if exp["deviceId"].(string) == deviceId { @@ -910,13 +910,13 @@ func TestNullComparisons(t *testing.T) { } } -// TestNullComparisonInAggregation 测试聚合查询中的 = nil 和 != nil +// TestNullComparisonInAggregation tests the aggregated queries for = nil and!= nil func TestNullComparisonInAggregation(t *testing.T) { t.Parallel() ssql := streamsql.New() defer ssql.Stop() - // 聚合查询:统计非空值的数量 + // Aggregate query: Counts the number of non-null values sql := `SELECT deviceType, COUNT(*) as total_count, COUNT(value) as non_null_count @@ -927,13 +927,13 @@ func TestNullComparisonInAggregation(t *testing.T) { err := ssql.Execute(sql) require.NoError(t, err) - // 收集结果 + // Collect the results resultChan := make(chan any, 10) ssql.Stream().AddSink(func(result []map[string]any) { resultChan <- result }) - // 添加测试数据 + // Add test data testData := []map[string]any{ {"deviceType": "temperature", "value": 25.5}, {"deviceType": "temperature", "value": nil}, @@ -946,16 +946,16 @@ func TestNullComparisonInAggregation(t *testing.T) { ssql.Stream().Emit(data) } - // 等待窗口触发 + // Wait for the window to trigger time.Sleep(3 * time.Second) - // 验证结果 + // Verify the results select { case result := <-resultChan: resultSlice, ok := result.([]map[string]any) require.True(t, ok, "结果应该是[]map[string]any类型") - // 应该有temperature和humidity两种类型的结果 + // There should be two types of results: temperature and humidity assert.GreaterOrEqual(t, len(resultSlice), 1, "应该至少有一个聚合结果") for _, item := range resultSlice { @@ -964,27 +964,27 @@ func TestNullComparisonInAggregation(t *testing.T) { nonNullCount, _ := item["non_null_count"].(float64) if deviceType == "temperature" { - // temperature有2个非空值(25.5, 27.0) + // Temperature has 2 non-null values (25.5, 27.0) assert.Equal(t, 2.0, totalCount, "temperature总数应该是2") assert.Equal(t, 2.0, nonNullCount, "temperature非空数应该是2") } else if deviceType == "humidity" { - // humidity有1个非空值(60.0) + // Humidity has 1 non-null value (60.0) assert.Equal(t, 1.0, totalCount, "humidity总数应该是1") assert.Equal(t, 1.0, nonNullCount, "humidity非空数应该是1") } } case <-time.After(5 * time.Second): - t.Fatal("测试超时,未收到聚合结果") + t.Fatal("The test timed out, and no aggregated results were received") } } -// TestMixedNullComparisons 测试混合使用 IS NULL、= nil、= null、!= null 等语法 +// TestMixedNullComparisons tests using syntax such as IS NULL, = nil, = null,!= null, etc func TestMixedNullComparisons(t *testing.T) { t.Parallel() ssql := streamsql.New() defer ssql.Stop() - // 测试混合null比较语法 + // Test mixed null comparative syntax sql := `SELECT deviceId, value, status, priority FROM stream WHERE (value IS NOT NULL AND value > 20) OR @@ -998,21 +998,21 @@ func TestMixedNullComparisons(t *testing.T) { resultChan <- result }) - // 添加测试数据 + // Add test data testData := []map[string]any{ - {"deviceId": "sensor1", "value": 25.0, "status": "active", "priority": "high"}, // 满足第一个条件 - {"deviceId": "sensor2", "value": 15.0, "status": "active", "priority": "low"}, // 不满足条件 - {"deviceId": "sensor3", "value": nil, "status": nil, "priority": "medium"}, // 满足第二个条件 - {"deviceId": "sensor4", "value": nil, "status": nil, "priority": nil}, // 不满足条件 - {"deviceId": "sensor5", "value": 30.0, "status": "inactive", "priority": nil}, // 满足第一个条件 - {"deviceId": "sensor6", "value": 10.0, "status": nil, "priority": "urgent"}, // 满足第二个条件 + {"deviceId": "sensor1", "value": 25.0, "status": "active", "priority": "high"}, // The first condition is met + {"deviceId": "sensor2", "value": 15.0, "status": "active", "priority": "low"}, // The conditions are not met + {"deviceId": "sensor3", "value": nil, "status": nil, "priority": "medium"}, // The second condition must be met + {"deviceId": "sensor4", "value": nil, "status": nil, "priority": nil}, // The conditions are not met + {"deviceId": "sensor5", "value": 30.0, "status": "inactive", "priority": nil}, // The first condition is met + {"deviceId": "sensor6", "value": 10.0, "status": nil, "priority": "urgent"}, // The second condition must be met } for _, data := range testData { ssql.Stream().Emit(data) } - // 使用超时方式安全收集结果 + // Safely collect results using timeout methods var results []map[string]any timeout := time.After(2 * time.Second) @@ -1028,7 +1028,7 @@ collecting: } } - // 验证结果:应该有sensor1, sensor3, sensor5, sensor6 + // Verification result: There should be sensor1, sensor3, sensor5, sensor6 assert.Len(t, results, 4, "应该有4个结果") expectedDeviceIds := []string{"sensor1", "sensor3", "sensor5", "sensor6"} diff --git a/test/e2e/plugin_test.go b/test/e2e/plugin_test.go index 126f6fa..f479b42 100644 --- a/test/e2e/plugin_test.go +++ b/test/e2e/plugin_test.go @@ -11,17 +11,17 @@ import ( "github.com/stretchr/testify/assert" ) -// 本文件测试串行执行(不加 t.Parallel):向全局 function registry 注册自定义函数, -// 与 custom_functions/quoted 等存在重名注册,并行会导致 "already registered" 冲突。 +// This file tests serial execution (without t.Parallel): registering a custom function in the global function registry, +// Duplicate registrations with custom_functions/quoted names and parallel will cause "already registered" conflicts. -// TestPluginStyleCustomFunctions 测试插件式自定义函数 +// TestPluginStyleCustomFunctions Tests plugin-style custom functions func TestPluginStyleCustomFunctions(t *testing.T) { - // 动态注册新函数(运行时注册,无需修改SQL解析代码) + // Dynamically registering new functions (registering at runtime, no need to modify SQL parsing code) - // 1. 注册字符串处理函数(应该直接处理,不需要窗口) + // 1. Register a string handler function (should handle it directly, no window needed) err := functions.RegisterCustomFunction( - "mask_phone", // 全新的函数名 + "mask_phone", // A brand new function name functions.TypeString, "数据脱敏", "手机号脱敏", @@ -37,7 +37,7 @@ func TestPluginStyleCustomFunctions(t *testing.T) { assert.NoError(t, err) defer functions.Unregister("mask_phone") - // 2. 注册转换函数(应该直接处理) + // 2. Register conversion functions (should be handled directly) err = functions.RegisterCustomFunction( "format_id", functions.TypeConversion, @@ -52,7 +52,7 @@ func TestPluginStyleCustomFunctions(t *testing.T) { assert.NoError(t, err) defer functions.Unregister("format_id") - // 3. 注册数学函数(用于窗口聚合) + // 3. Register Math Functions (for window aggregation) err = functions.RegisterCustomFunction( "calculate_commission", functions.TypeMath, @@ -68,13 +68,13 @@ func TestPluginStyleCustomFunctions(t *testing.T) { assert.NoError(t, err) defer functions.Unregister("calculate_commission") - // 测试1:纯字符串函数(不需要窗口) + // Test 1: Pure string function (no window required) testStringFunctionsOnly(t) - // 测试2:转换函数(不需要窗口) + // Test 2: Conversion function (no window required) testConversionFunctionsOnly(t) - // 测试3:数学函数在聚合中使用(需要窗口) + // Test 3: Using Mathematical Functions in Aggregation (Window Required) testMathFunctionsInAggregate(t) } @@ -99,7 +99,7 @@ func testStringFunctionsOnly(t *testing.T) { resultChan <- result }) - // 添加测试数据 + // Add test data testData := map[string]any{ "employee_id": "E001", "phone": "13812345678", @@ -116,10 +116,10 @@ func testStringFunctionsOnly(t *testing.T) { item := resultSlice[0] assert.Equal(t, "E001", item["employee_id"]) - assert.Equal(t, "138****5678", item["masked_phone"]) // 脱敏后的手机号 + assert.Equal(t, "138****5678", item["masked_phone"]) // The desensitized phone number case <-time.After(2 * time.Second): - t.Fatal("字符串函数测试超时") + t.Fatal("String function test timeout") } } @@ -143,7 +143,7 @@ func testConversionFunctionsOnly(t *testing.T) { resultChan <- result }) - // 添加测试数据 + // Add test data testData := map[string]any{ "user_id": "12345", } @@ -163,7 +163,7 @@ func testConversionFunctionsOnly(t *testing.T) { fmt.Printf(" 📊 转换函数结果: %v\n", item) case <-time.After(2 * time.Second): - t.Fatal("转换函数测试超时") + t.Fatal("Conversion function test timeout") } } @@ -188,7 +188,7 @@ func testMathFunctionsInAggregate(t *testing.T) { resultChan <- result }) - // 添加测试数据 + // Add test data testData := []map[string]any{ { "department": "sales", @@ -219,23 +219,23 @@ func testMathFunctionsInAggregate(t *testing.T) { item := resultSlice[0] assert.Equal(t, "sales", item["department"]) - // 验证聚合计算结果 + // Verify aggregated calculation results avgCommission, ok := item["avg_commission"].(float64) assert.True(t, ok) expectedAvg := (8000*3/100 + 12000*4/100) / 2 // (240 + 480) / 2 = 360 assert.InEpsilon(t, expectedAvg, avgCommission, 0.01) case <-time.After(3 * time.Second): - t.Fatal("聚合数学函数测试超时") + t.Fatal("Aggregate math function test timed out") } } -// TestRuntimeFunctionManagement 测试运行时函数管理 +// TestRuntimeFunctionManagement Manages the runtime function for testing func TestRuntimeFunctionManagement(t *testing.T) { - // 动态注册函数 + // Dynamic registration function err := functions.RegisterCustomFunction( "temp_function", - functions.TypeString, // 使用字符串类型以便直接处理 + functions.TypeString, // String types are used for direct processing "临时函数", "临时测试函数", 1, 1, @@ -246,12 +246,12 @@ func TestRuntimeFunctionManagement(t *testing.T) { ) assert.NoError(t, err) - // 验证函数已注册 + // The verification function has been registered fn, exists := functions.Get("temp_function") assert.True(t, exists) assert.Equal(t, "temp_function", fn.GetName()) - // 在SQL中使用 + // Used in SQL ssql := streamsql.New() defer ssql.Stop() @@ -274,21 +274,21 @@ func TestRuntimeFunctionManagement(t *testing.T) { assert.Len(t, resultSlice, 1) assert.Equal(t, "TEMP_test", resultSlice[0]["result"]) case <-time.After(2 * time.Second): - t.Fatal("运行时函数管理测试超时") + t.Fatal("Runtime function management tests timed out") } - // 运行时注销函数 + // Runtime logout function success := functions.Unregister("temp_function") assert.True(t, success) - // 验证函数已注销 + // The validation function has been logged off _, exists = functions.Get("temp_function") assert.False(t, exists) } -// TestFunctionPluginDiscovery 测试函数插件发现机制 +// TestFunctionPluginDiscovery Discovers the mechanism of the test function plugin func TestFunctionPluginDiscovery(t *testing.T) { - // 注册不同类型的函数 + // Register functions of different types functions.RegisterCustomFunction("plugin_math", functions.TypeMath, "插件", "数学插件", 1, 1, func(ctx *functions.FunctionContext, args []any) (any, error) { return args[0], nil @@ -302,11 +302,11 @@ func TestFunctionPluginDiscovery(t *testing.T) { defer functions.Unregister("plugin_math") defer functions.Unregister("plugin_string") - // 测试按类型发现函数 + // Testing functions by type mathFunctions := functions.GetByType(functions.TypeMath) assert.Greater(t, len(mathFunctions), 0) - // 验证新注册的函数被发现 + // Verification of newly registered functions is discovered found := false for _, fn := range mathFunctions { if fn.GetName() == "plugin_math" { @@ -316,16 +316,16 @@ func TestFunctionPluginDiscovery(t *testing.T) { } assert.True(t, found, "新注册的数学函数应该被发现") - // 测试全量函数发现 + // Testing the full function reveals this allFunctions := functions.ListAll() assert.Contains(t, allFunctions, "plugin_math") assert.Contains(t, allFunctions, "plugin_string") } -// TestCompleteSQLIntegration 测试完整的SQL集成 +// TestCompleteSQLIntegration tests complete SQL integration func TestCompleteSQLIntegration(t *testing.T) { - // 注册完全新的业务函数 + // Register a completely new business function err := functions.RegisterCustomFunction( "business_metric", functions.TypeString, @@ -355,7 +355,7 @@ func TestCompleteSQLIntegration(t *testing.T) { ssql := streamsql.New() defer ssql.Stop() - // 使用全新的函数在SQL中 + // Use brand-new functions in SQL sql := ` SELECT customer_id, @@ -391,6 +391,6 @@ func TestCompleteSQLIntegration(t *testing.T) { assert.Equal(t, "premium:150.00", item["metric"]) case <-time.After(2 * time.Second): - t.Fatal("完整SQL集成测试超时") + t.Fatal("Full SQL integration test timeout") } } diff --git a/test/e2e/post_aggregation_test.go b/test/e2e/post_aggregation_test.go index 47d6dfd..5978c36 100644 --- a/test/e2e/post_aggregation_test.go +++ b/test/e2e/post_aggregation_test.go @@ -11,7 +11,7 @@ import ( "github.com/stretchr/testify/require" ) -// 辅助函数:创建测试环境 +// Auxiliary function: Creates test environments func createTestEnvironment(t *testing.T, rsql string) (*streamsql.Streamsql, chan any) { ssql := streamsql.New() t.Cleanup(func() { ssql.Stop() }) @@ -25,34 +25,34 @@ func createTestEnvironment(t *testing.T, rsql string) (*streamsql.Streamsql, cha ssql.AddSink(func(result []map[string]any) { defer func() { if r := recover(); r != nil { - // channel 已关闭,忽略错误 + // channel is closed, ignoring errors } }() select { case resultChan <- result: default: - // 非阻塞发送 + // Non-blocking transmission } }) return ssql, resultChan } -// 辅助函数:发送测试数据并收集结果 +// Auxiliary function: Send test data and collect results func sendDataAndCollectResults(t *testing.T, ssql *streamsql.Streamsql, resultChan chan any, testData []map[string]any, windowSizeSeconds int) []map[string]any { for _, data := range testData { ssql.Emit(data) } - // 等待窗口触发 + // Wait for the window to trigger time.Sleep(time.Duration(windowSizeSeconds+1) * time.Second) - // 使用更严格的超时机制 + // Uses stricter timeout mechanisms ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() var results []map[string]any - maxIterations := 10 // 最多收集10次结果 + maxIterations := 10 // Collect results up to 10 times iteration := 0 collecting: @@ -64,10 +64,10 @@ collecting: } iteration++ case <-time.After(500 * time.Millisecond): - // 500ms 没有新结果,退出 + // Exit after 500 ms without a new result break collecting case <-ctx.Done(): - // 超时退出 + // Exiting after the time limit break collecting } } @@ -75,7 +75,7 @@ collecting: return results } -// TestPostAggregationExpressions 测试分阶段聚合功能 +// TestPostAggregationExpressions tests the staged aggregation feature func TestPostAggregationExpressions(t *testing.T) { t.Parallel() t.Run("基础聚合函数复杂运算", func(t *testing.T) { @@ -102,7 +102,7 @@ func TestPostAggregationExpressions(t *testing.T) { require.Len(t, results, 1) result := results[0] - // 验证基础聚合函数运算 + // Verify basic aggregate function operations assert.Equal(t, "dev1", result["deviceId"]) assert.Equal(t, 10.0, result["firstVal"]) assert.Equal(t, 30.0, result["lastVal"]) @@ -113,7 +113,7 @@ func TestPostAggregationExpressions(t *testing.T) { assert.Equal(t, 80.0, result["sumPlusAvg"]) // SUM + AVG }) - // IF_NULL 基础功能:在 IF_NULL 中包裹聚合/分析函数 + // IF_NULL Basic function: Wrap aggregation/analysis functions in IF_NULL t.Run("验证:IF_NULL 基础功能", func(t *testing.T) { rsql := `SELECT deviceId, IF_NULL(FIRST_VALUE(value), 0) as firstOrZero, @@ -136,15 +136,15 @@ func TestPostAggregationExpressions(t *testing.T) { result := results[0] assert.Equal(t, "sensor1", result["deviceId"]) - // FIRST_VALUE(value) 为 nil => IF_NULL(...,0) = 0 + // FIRST_VALUE(value) is nil = > IF_NULL(...,0) = 0 assert.Equal(t, 0.0, result["firstOrZero"]) - // LAST_VALUE(value) 为 30 => IF_NULL(...,0) = 30 + // LAST_VALUE(value) is 30 = > IF_NULL(...,0) = 30 assert.Equal(t, 30.0, result["lastOrZero"]) - // AVG(value) 仅计算非空 => (10+30)/2 = 20 => IF_NULL(...,0) = 20 + // AVG(value) only calculates non-null = >(10+30)/2 = 20 = > IF_NULL(...,0) = 20 assert.Equal(t, 20.0, result["avgOrZero"]) }) - // 聚合函数参数中嵌套 IF_NULL:如 SUM(IF_NULL(value,0)) + // Nested IF_NULL in aggregate function parameters: such as SUM(IF_NULL(value, 0)) t.Run("验证:聚合函数嵌套 IF_NULL", func(t *testing.T) { rsql := `SELECT deviceId, SUM(IF_NULL(value, 0)) as sumVal, @@ -179,7 +179,7 @@ func TestPostAggregationExpressions(t *testing.T) { }) t.Run("分析函数与聚合函数复杂运算", func(t *testing.T) { - t.Skip("v1.2 分析函数改为直连 OVER 语义,不再与 GROUP BY/聚合运算混用;混合运算见后续版本") + t.Skip("v1.2 The analysis function was changed to a direct OVER semantic, no longer mixed with GROUP BY/ aggregation operations; Hybrid operations are available in future versions") rsql := `SELECT deviceId, SUM(value) as total, AVG(value) as average, @@ -201,11 +201,11 @@ func TestPostAggregationExpressions(t *testing.T) { require.Len(t, results, 1) result := results[0] - // 验证分析函数与聚合函数的复杂运算 + // Verify the complex operations of the analysis function and the aggregation function assert.Equal(t, "sensor1", result["deviceId"]) assert.Equal(t, 60.0, result["total"]) // 10+20+30 assert.Equal(t, 20.0, result["average"]) // 60/3 - assert.Equal(t, 30.0, result["latest"]) // 最新值 + assert.Equal(t, 30.0, result["latest"]) // Latest values assert.Equal(t, 90.0, result["totalPlusLatest"]) // 60 + 30 assert.Equal(t, 600.0, result["avgTimesLatest"]) // 20 * 30 }) @@ -238,14 +238,14 @@ func TestPostAggregationExpressions(t *testing.T) { require.Len(t, results, 1) result := results[0] - // 验证基础函数 + // Verify the basic function assert.Equal(t, "sensor1", result["deviceId"]) assert.Equal(t, 100.0, result["total"]) // 10+20+30+40 assert.Equal(t, 4.0, result["count"]) // 4 records assert.Equal(t, 25.0, result["average"]) // 100/4 assert.Equal(t, 40.0, result["maxVal"]) // max value - // 验证最外层嵌套普通函数 + // Verify the outermost nested ordinary function // (COUNT(*) * AVG(value)) = 4 * 25 = 100 assert.Equal(t, 100.0, result["countTimesAvg"], "最外层嵌套函数计算错误") @@ -255,7 +255,7 @@ func TestPostAggregationExpressions(t *testing.T) { // ((COUNT(*) + SUM(value)) * AVG(value)) = (4 + 100) * 25 = 2600 assert.Equal(t, 2600.0, result["complexNested"], "最外层复杂嵌套函数计算错误") - // 验证最外层嵌套普通函数 + // Verify the outermost nested ordinary function // FLOOR((SUM(value) / MAX(value))) = FLOOR(100/40) = FLOOR(2.5) = 2 if floorResult, ok := result["floorResult"].(float64); ok { assert.Equal(t, 2.0, floorResult, "FLOOR函数嵌套计算错误") @@ -271,7 +271,7 @@ func TestPostAggregationExpressions(t *testing.T) { assert.Equal(t, 2.5, roundResult, "ROUND函数嵌套计算错误") } - // 验证最外层嵌套普通函数的正确性 + // Verify the correctness of the outermost nested ordinary function assert.Equal(t, 100.0, result["countTimesAvg"], "COUNT(*) * AVG(value) 计算错误") assert.Equal(t, 2.5, result["sumDivideMax"], "SUM(value) / MAX(value) 计算错误") assert.Equal(t, 2600.0, result["complexNested"], "复杂嵌套表达式计算错误") @@ -291,11 +291,11 @@ func TestPostAggregationExpressions(t *testing.T) { ssql, resultChan := createTestEnvironment(t, rsql) testData := []map[string]any{ - // 设备1的数据 + // Data from device 1 {"deviceId": "meter001", "displayNum": 100.0}, {"deviceId": "meter001", "displayNum": 115.0}, - // 设备2的数据 + // Data from device 2 {"deviceId": "meter002", "displayNum": 200.0}, {"deviceId": "meter002", "displayNum": 206.0}, } @@ -303,13 +303,13 @@ func TestPostAggregationExpressions(t *testing.T) { results := sendDataAndCollectResults(t, ssql, resultChan, testData, 5) require.GreaterOrEqual(t, len(results), 1, "应该至少有一个窗口的结果") - // 预期结果 + // Expected results expectedDiffs := map[string]float64{ "meter001": 15.0, // 115.0 - 100.0 = 15.0 kWh "meter002": 6.0, // 206.0 - 200.0 = 6.0 kWh } - // 验证每个设备的计算结果 + // Verify the calculation results for each device deviceResults := make(map[string]map[string]any) for _, result := range results { deviceId, ok := result["deviceId"].(string) @@ -328,17 +328,17 @@ func TestPostAggregationExpressions(t *testing.T) { "设备 %s 的用电量计算应该正确: 期望 %.1f, 实际 %.1f", deviceId, expectedDiff, diffVal) - // 验证窗口时间字段存在 + // Verify the existence of the window time field assert.Contains(t, result, "start", "结果应包含窗口开始时间") assert.Contains(t, result, "end", "结果应包含窗口结束时间") } } - // 原始问题验证成功:电表读数差值计算正确 + // Original issue verified successfully: meter reading difference calculation is correct }) t.Run("综合功能验证", func(t *testing.T) { - t.Skip("v1.2 分析函数改为直连 OVER 语义,不再与 GROUP BY/聚合运算混用;混合运算见后续版本") + t.Skip("v1.2 The analysis function was changed to a direct OVER semantic, no longer mixed with GROUP BY/ aggregation operations; Hybrid operations are available in future versions") rsql := `SELECT deviceId, SUM(value) as total, AVG(value) as average, @@ -370,30 +370,30 @@ func TestPostAggregationExpressions(t *testing.T) { require.Len(t, results, 1) result := results[0] - // 验证基础函数 + // Verify the basic function assert.Equal(t, "sensor1", result["deviceId"]) assert.Equal(t, 100.0, result["total"]) // 10+20+30+40 assert.Equal(t, 25.0, result["average"]) // 100/4 - assert.Equal(t, 10.0, result["first"]) // 第一个值 - assert.Equal(t, 40.0, result["last"]) // 最后一个值 - assert.Equal(t, 40.0, result["latest"]) // 最新值 - assert.Equal(t, 4.0, result["count"]) // 4条记录 - assert.Equal(t, 40.0, result["maxVal"]) // 最大值 - assert.Equal(t, 10.0, result["minVal"]) // 最小值 - - // 验证复杂表达式计算 + assert.Equal(t, 10.0, result["first"]) // The first value + assert.Equal(t, 40.0, result["last"]) // The last value + assert.Equal(t, 40.0, result["latest"]) // Latest values + assert.Equal(t, 4.0, result["count"]) // 4 records + assert.Equal(t, 40.0, result["maxVal"]) // Maximum value + assert.Equal(t, 10.0, result["minVal"]) // Minimum value + + // Verify complex expression calculations assert.Equal(t, 27.5, result["complexCalc1"]) // (100 + 10) / 4 = 27.5 assert.Equal(t, 990.0, result["complexCalc2"]) // 40 * 25 - 10 = 990 assert.Equal(t, 35.0, result["complexCalc3"]) // (40 + 100) / 4 = 35 assert.Equal(t, 25.0, result["complexCalc4"]) // 40 + 10 - 25 = 25 - // 验证多层嵌套数学函数 + // Verify multi-layer nested mathematical functions // ROUND(SQRT(ABS(AVG(value) - MIN(value))), 2) = ROUND(SQRT(ABS(25-10)), 2) = ROUND(SQRT(15), 2) ≈ 3.87 if nestedMathFunc, ok := result["nestedMathFunc"].(float64); ok { assert.InEpsilon(t, 3.87, nestedMathFunc, 0.01, "多层嵌套数学函数计算错误") } - // 验证多层嵌套字符串和数学函数 + // Verify multi-layer nested strings and mathematical functions // UPPER(CONCAT('RESULT_', CAST(ROUND(SUM(value), 0) as STRING))) = UPPER(CONCAT('RESULT_', '100')) = 'RESULT_100' if nestedStrMathFunc, ok := result["nestedStrMathFunc"].(string); ok { assert.Equal(t, "RESULT_100", nestedStrMathFunc, "多层嵌套字符串和数学函数计算错误") @@ -430,7 +430,7 @@ func TestPostAggregationExpressions(t *testing.T) { require.Len(t, results, 1) result := results[0] - // 验证基础聚合函数 + // Verify the basic aggregation function assert.Equal(t, "sensor1", result["deviceId"]) assert.Equal(t, 126.0, result["total"]) // 16+25+36+49 assert.Equal(t, 31.5, result["average"]) // 126/4 @@ -438,7 +438,7 @@ func TestPostAggregationExpressions(t *testing.T) { assert.Equal(t, 49.0, result["maxVal"]) // max value assert.Equal(t, 16.0, result["minVal"]) // min value - // 验证嵌套聚合函数运算 + // Verify nested aggregate function operations // ROUND(AVG(ABS(value)), 2) = ROUND(AVG(16,25,36,49), 2) = ROUND(31.5, 2) = 31.5 if avgAbs, ok := result["avgAbs"].(float64); ok { assert.Equal(t, 31.5, avgAbs, "AVG(ABS(value))计算错误") @@ -503,12 +503,12 @@ func TestPostAggregationExpressions(t *testing.T) { require.Len(t, results, 1) result := results[0] - // 验证基础函数 + // Verify the basic function assert.Equal(t, "sensor1", result["deviceId"]) assert.Equal(t, 100.0, result["total"]) // 10+20+30+40 assert.Equal(t, 4.0, result["count"]) // 4 records - // 验证窗口函数基础功能 + // Verify the basic functions of the window function assert.NotNil(t, result["countTimesSecond"], "COUNT(*) * NTH_VALUE(value, 2) 应该有计算结果") }) @@ -535,9 +535,9 @@ func TestPostAggregationExpressions(t *testing.T) { require.Len(t, results, 1) result := results[0] - // 验证 NTH_VALUE 函数的返回值 - // 期望结果:按添加顺序 - // 第1个值: 100, 第2个值: 200, 第3个值: 300, 第4个值: 400 + // Verify the return value of the NTH_VALUE function + // Expected result: in order of addition + // First value: 100, Second value: 200, Third value: 300, Fourth value: 400 if firstValue, ok := result["firstValue"].(float64); ok { assert.Equal(t, 100.0, firstValue, "第1个值应该是100") } else { diff --git a/test/e2e/quoted_test.go b/test/e2e/quoted_test.go index 1f95c5a..e3de886 100644 --- a/test/e2e/quoted_test.go +++ b/test/e2e/quoted_test.go @@ -12,7 +12,7 @@ import ( "github.com/stretchr/testify/assert" ) -// testCase 定义测试用例结构 +// testCase defines the test case structure type testCase struct { name string sql string @@ -21,10 +21,10 @@ type testCase struct { validator func(t *testing.T, results []map[string]any) } -// executeTestCase 执行单个测试用例的通用逻辑 +// executeTestCase executes the general logic for a single test case func executeTestCase(t *testing.T, ssql *streamsql.Streamsql, tc testCase) { t.Run(tc.name, func(t *testing.T) { - // 为每个测试用例创建新的Streamsql实例 + // Create a new StreamSQL instance for each test case ssql := streamsql.New() defer ssql.Stop() @@ -32,7 +32,7 @@ func executeTestCase(t *testing.T, ssql *streamsql.Streamsql, tc testCase) { assert.Nil(t, err) strm := ssql.Stream() - // 创建结果接收通道和互斥锁保护并发访问 + // Create result receiving channels and mutex protection for concurrent access resultChan := make(chan any, 10) var results []map[string]any var resultsMutex sync.Mutex @@ -41,19 +41,19 @@ func executeTestCase(t *testing.T, ssql *streamsql.Streamsql, tc testCase) { select { case resultChan <- result: default: - // 通道满时丢弃结果,避免阻塞 + // Discard results when the channel is full, avoiding blockage } }) - // 添加测试数据 + // Add test data for _, data := range tc.testData { strm.Emit(data) } - // 等待数据处理 + // Waiting for data processing time.Sleep(200 * time.Millisecond) - // 收集所有结果 + // Collect all the results timeout := time.After(2 * time.Second) for { resultsMutex.Lock() @@ -79,24 +79,24 @@ func executeTestCase(t *testing.T, ssql *streamsql.Streamsql, tc testCase) { } checkResults: - // 验证结果长度(使用互斥锁保护) + // Verification result length (using mutex protection) resultsMutex.Lock() finalResults := make([]map[string]any, len(results)) copy(finalResults, results) resultsMutex.Unlock() assert.Equal(t, tc.expectedLen, len(finalResults)) - // 执行自定义验证 + // Perform custom validation if tc.validator != nil { tc.validator(t, finalResults) } }) } -// executeAggregationTestCase 执行聚合函数测试用例的通用逻辑 +// executeAggregationTestCase executes the general logic for aggregation function test cases func executeAggregationTestCase(t *testing.T, ssql *streamsql.Streamsql, tc testCase) { t.Run(tc.name, func(t *testing.T) { - // 为每个测试用例创建新的Streamsql实例 + // Create a new StreamSQL instance for each test case ssql := streamsql.New() defer ssql.Stop() @@ -104,42 +104,42 @@ func executeAggregationTestCase(t *testing.T, ssql *streamsql.Streamsql, tc test assert.Nil(t, err) strm := ssql.Stream() - // 创建结果接收通道 + // Create a result receiving channel resultChan := make(chan any, 10) strm.AddSink(func(result []map[string]any) { select { case resultChan <- result: default: - // 通道满时丢弃结果,避免阻塞 + // Discard results when the channel is full, avoiding blockage } }) - // 添加测试数据 + // Add test data for _, data := range tc.testData { strm.Emit(data) } - // 等待窗口触发 + // Wait for the window to trigger time.Sleep(1 * time.Second) strm.Window.Trigger() time.Sleep(500 * time.Millisecond) - // 验证结果 + // Verify the results select { case result := <-resultChan: if tc.validator != nil { tc.validator(t, result.([]map[string]any)) } case <-time.After(3 * time.Second): - t.Fatal("测试超时") + t.Fatal("Test timeout") } }) } -// executeFunctionTestCase 执行函数测试用例的通用逻辑 +// executeFunctionTestCase executes the general logic of the function test case func executeFunctionTestCase(t *testing.T, ssql *streamsql.Streamsql, tc testCase) { t.Run(tc.name, func(t *testing.T) { - // 为每个测试用例创建新的Streamsql实例 + // Create a new StreamSQL instance for each test case ssql := streamsql.New() defer ssql.Stop() @@ -147,55 +147,55 @@ func executeFunctionTestCase(t *testing.T, ssql *streamsql.Streamsql, tc testCas assert.Nil(t, err) strm := ssql.Stream() - // 创建结果接收通道 + // Create a result receiving channel resultChan := make(chan any, 10) strm.AddSink(func(result []map[string]any) { select { case resultChan <- result: default: - // 通道满时丢弃结果,避免阻塞 + // Discard results when the channel is full, avoiding blockage } }) - // 添加测试数据 + // Add test data for _, data := range tc.testData { strm.Emit(data) } time.Sleep(200 * time.Millisecond) - // 验证结果 + // Verify the results select { case result := <-resultChan: if tc.validator != nil { tc.validator(t, result.([]map[string]any)) } case <-time.After(2 * time.Second): - t.Fatal("测试超时") + t.Fatal("Test timeout") } }) } -// 本文件测试串行执行(不加 t.Parallel):向全局 function registry 注册自定义函数(func01/func02/get_type 等), -// 与 custom_functions 等存在重名注册,并行会导致 "already registered" 冲突。 +// This file tests serial execution (without t.Parallel): registers custom functions (func01/func02/get_type, etc.) with the global function registry, +// There are duplicate registrations with custom_functions and others, and parallelism will cause a "already registered" conflict. -// TestQuotedIdentifiersAndStringLiterals 测试反引号标识符和字符串常量支持 +// TestQuotedIdentifiersAndStringLiterals tests backquoted identifiers and string constant support func TestQuotedIdentifiersAndStringLiterals(t *testing.T) { - // 注册测试函数(因为有测试用例使用自定义函数) + // Register test functions (because there are test cases using custom functions) registerTestFunctions(t) defer unregisterTestFunctions() ssql := streamsql.New() defer ssql.Stop() - // 通用测试数据 + // General test data standardTestData := []map[string]any{ {"deviceId": "sensor001", "deviceType": "temperature"}, {"deviceId": "device002", "deviceType": "humidity"}, {"deviceId": "sensor003", "deviceType": "pressure"}, } - // 定义测试用例 + // Define test cases testCases := []testCase{ { name: "反引号标识符支持", @@ -267,25 +267,25 @@ func TestQuotedIdentifiersAndStringLiterals(t *testing.T) { }, } - // 执行所有测试用例 + // Execute all test cases for _, tc := range testCases { executeTestCase(t, ssql, tc) } } -// TestStringConstantExpressions 测试字符串常量表达式 +// TestStringConstantExpressions tests string constant expressions func TestStringConstantExpressions(t *testing.T) { ssql := streamsql.New() defer ssql.Stop() - // 通用测试数据 + // General test data testData := []map[string]any{ {"deviceId": "sensor001", "deviceType": "temperature"}, {"deviceId": "device002", "deviceType": "humidity"}, {"deviceId": "sensor003", "deviceType": "pressure"}, } - // 字符串常量验证函数 + // String constant verification function stringConstantValidator := func(expectedValue string) func(t *testing.T, results []map[string]any) { return func(t *testing.T, results []map[string]any) { for _, result := range results { @@ -313,28 +313,28 @@ func TestStringConstantExpressions(t *testing.T) { }, } - // 执行所有测试用例 + // Execute all test cases for _, tc := range testCases { executeTestCase(t, ssql, tc) } } -// TestAggregationWithQuotedIdentifiers 测试聚合函数与反引号标识符的结合使用 +// TestAggregationWithQuotedIdentifiers tests the combination of aggregation functions and backquoted identifiers func TestAggregationWithQuotedIdentifiers(t *testing.T) { ssql := streamsql.New() defer ssql.Stop() - // 聚合测试数据 + // Aggregate test data aggregationTestData := []map[string]any{ {"deviceId": "sensor001", "temperature": 25.5}, {"deviceId": "sensor001", "temperature": 26.0}, {"deviceId": "sensor002", "temperature": 30.0}, } - // 聚合结果验证函数 + // Aggregate result verification function aggregationValidator := func(t *testing.T, results []map[string]any) { resultSlice := results - assert.Len(t, resultSlice, 2) // 应该有两个设备的聚合结果 + assert.Len(t, resultSlice, 2) // There should be an aggregated result of two devices for _, item := range resultSlice { if item["deviceId"] == "sensor001" { @@ -356,15 +356,15 @@ func TestAggregationWithQuotedIdentifiers(t *testing.T) { }, } - // 执行所有聚合测试用例 + // Execute all aggregated test cases for _, tc := range testCases { executeAggregationTestCase(t, ssql, tc) } } -// TestCustomFunctionWithQuotedIdentifiers 测试自定义函数与反引号标识符和字符串常量的参数传递 +// TestCustomFunctionWithQuotedIdentifiers Tests the parameter passing between the custom function and the backquote identifier and string constants func TestCustomFunctionWithQuotedIdentifiers(t *testing.T) { - // 注册测试函数 + // Register the test function registerTestFunctions(t) defer unregisterTestFunctions() @@ -414,15 +414,15 @@ func TestCustomFunctionWithQuotedIdentifiers(t *testing.T) { }, } - // 执行所有函数测试用例 + // Execute all function test cases for _, tc := range testCases { executeFunctionTestCase(t, ssql, tc) } } -// registerTestFunctions 注册测试用的自定义函数 +// registerTestFunctions: A custom function used for registering tests func registerTestFunctions(t *testing.T) { - // 注册测试函数:接收字段值并返回其平方 + // Register test function: Receives field values and returns their square err := functions.RegisterCustomFunction( "func01", functions.TypeMath, @@ -436,7 +436,7 @@ func registerTestFunctions(t *testing.T) { ) assert.NoError(t, err) - // 注册测试函数:接收字符串并返回其长度 + // Register test function: Receive the string and return its length err = functions.RegisterCustomFunction( "func02", functions.TypeString, @@ -450,7 +450,7 @@ func registerTestFunctions(t *testing.T) { ) assert.NoError(t, err) - // 注册测试函数:接收参数并返回其类型信息 + // Register test function: Receives parameters and returns their type information err = functions.RegisterCustomFunction( "get_type", functions.TypeCustom, @@ -464,7 +464,7 @@ func registerTestFunctions(t *testing.T) { assert.NoError(t, err) } -// unregisterTestFunctions 注销测试用的自定义函数 +// unregisterTestFunctions: A custom function used to log out of testing func unregisterTestFunctions() { functions.Unregister("func01") functions.Unregister("func02") diff --git a/test/e2e/select_test.go b/test/e2e/select_test.go index 6de918b..fc573dc 100644 --- a/test/e2e/select_test.go +++ b/test/e2e/select_test.go @@ -6,9 +6,9 @@ import ( "github.com/rulego/streamsql" ) -// TestSelectAllAsterisk 锁定 SELECT * 行为:返回输入行的全部字段。 -// 回归保护:parser 曾把 * 误判为 TokenIdent(实为 TokenAsterisk), -// 导致 SelectAll 标志永不置真(死代码)。修复后 SELECT * 仍应输出所有字段。 +// TestSelectAllAsterisk Lock SELECT * behavior: Returns all fields in the input row. +// Regression protection: parser once misjudged * as TokenIdent (actually TokenAsterisk), +// This causes the SelectAll flag to never be true (dead code). After fixing, SELECT * should still output all fields. func TestSelectAllAsterisk(t *testing.T) { t.Parallel() ssql := streamsql.New() @@ -34,7 +34,7 @@ func TestSelectAllAsterisk(t *testing.T) { } } -// TestSelectAllWithWhere 验证 SELECT * 与 WHERE 共存:先过滤再输出全部字段。 +// TestSelectAllWithWhere Verify that SELECT * coexists with WHERE: filter first, then output all fields. func TestSelectAllWithWhere(t *testing.T) { t.Parallel() ssql := streamsql.New() diff --git a/test/e2e/sink_test.go b/test/e2e/sink_test.go index 509e746..3e0f898 100644 --- a/test/e2e/sink_test.go +++ b/test/e2e/sink_test.go @@ -27,25 +27,25 @@ import ( "github.com/stretchr/testify/require" ) -// TestEmitSyncWithAddSink 测试EmitSync同时触发AddSink回调 +// TestEmitSyncWithAddSink TestEmitSync simultaneously triggers the AddSink callback func TestEmitSyncWithAddSink(t *testing.T) { t.Parallel() t.Run("非聚合查询同步+异步结果", func(t *testing.T) { ssql := streamsql.New() defer ssql.Stop() - // 执行非聚合查询 - 测试反引号字段与字符串常量的混合用法 + // Perform non-aggregated queries - test the mixed use of backquoted fields and string constants sql := "SELECT `temperature`, humidity, `temperature` * 1.8 + 32 as temp_fahrenheit, 'normal' as status, 'sensor_data' as data_type FROM stream WHERE temperature > 20" err := ssql.Execute(sql) require.NoError(t, err) - // 验证是非聚合查询 + // Verify that it is a non-aggregated query assert.False(t, ssql.IsAggregationQuery()) - // 设置AddSink回调来收集异步结果 + // Set AddSink callbacks to collect asynchronous results var sinkCallCount int32 var sinkResults []any - var sinkResultsMux sync.Mutex // 保护sinkResults访问 + var sinkResultsMux sync.Mutex // Protect sinkResults access ssql.AddSink(func(result []map[string]any) { atomic.AddInt32(&sinkCallCount, 1) sinkResultsMux.Lock() @@ -53,18 +53,18 @@ func TestEmitSyncWithAddSink(t *testing.T) { sinkResultsMux.Unlock() }) - // 测试数据 + // Test data testData := []map[string]any{ - {"temperature": 25.0, "humidity": 60.0}, // 符合条件 - {"temperature": 15.0, "humidity": 70.0}, // 被过滤 - {"temperature": 30.0, "humidity": 80.0}, // 符合条件 + {"temperature": 25.0, "humidity": 60.0}, // Meet the requirements + {"temperature": 15.0, "humidity": 70.0}, // Filtered + {"temperature": 30.0, "humidity": 80.0}, // Meet the requirements } var syncResults []map[string]any - // 处理测试数据 + // Processing test data for _, data := range testData { - // 同步处理 + // Synchronized processing result, err := ssql.EmitSync(data) require.NoError(t, err) @@ -73,46 +73,46 @@ func TestEmitSyncWithAddSink(t *testing.T) { } } - // 等待异步回调完成 + // Wait for the asynchronous callback to complete time.Sleep(100 * time.Millisecond) - // 验证同步结果 + // Verify synchronization results assert.Equal(t, 2, len(syncResults), "应该有2条同步结果(温度>20)") - // 安全读取异步回调结果 + // Safely read asynchronous callback results sinkResultsMux.Lock() finalSinkResults := make([]any, len(sinkResults)) copy(finalSinkResults, sinkResults) sinkResultsMux.Unlock() - // 验证异步回调结果 + // Verify the asynchronous callback results finalSinkCallCount := atomic.LoadInt32(&sinkCallCount) assert.Equal(t, int32(2), finalSinkCallCount, "AddSink应该被调用2次") assert.Equal(t, 2, len(finalSinkResults), "应该收集到2条异步结果") - // 验证同步和异步结果的内容一致性 + // Verify the consistency between synchronous and asynchronous results if len(syncResults) > 0 && len(finalSinkResults) > 0 { - // 将结果转换为可比较的格式 + // Convert the results into comparable formats syncTemperatures := make([]float64, 0, len(syncResults)) syncHumidities := make([]float64, 0, len(syncResults)) asyncTemperatures := make([]float64, 0, len(finalSinkResults)) asyncHumidities := make([]float64, 0, len(finalSinkResults)) - // 收集同步结果 + // Collect synchronized results for _, result := range syncResults { syncResult := result syncTemperatures = append(syncTemperatures, syncResult["temperature"].(float64)) syncHumidities = append(syncHumidities, syncResult["humidity"].(float64)) - // 验证字符串常量字段 + // Validate string constant fields assert.Equal(t, "normal", syncResult["status"], "status字段应该是常量'normal'") assert.Equal(t, "sensor_data", syncResult["data_type"], "data_type字段应该是常量'sensor_data'") - // 验证反引号字段的数学运算 + // Verify the mathematical operations of the backtick field expectedFahrenheit := syncResult["temperature"].(float64)*1.8 + 32 assert.InDelta(t, expectedFahrenheit, syncResult["temp_fahrenheit"].(float64), 0.01, "华氏温度转换应该正确") - // 验证结果包含所有预期字段 + // The validation result includes all expected fields assert.Contains(t, syncResult, "temperature", "应该包含temperature字段") assert.Contains(t, syncResult, "humidity", "应该包含humidity字段") assert.Contains(t, syncResult, "temp_fahrenheit", "应该包含temp_fahrenheit字段") @@ -120,7 +120,7 @@ func TestEmitSyncWithAddSink(t *testing.T) { assert.Contains(t, syncResult, "data_type", "应该包含data_type字段") } - // 收集异步结果 + // Collect asynchronous results for _, result := range finalSinkResults { if sinkResultArray, ok := result.([]map[string]any); ok && len(sinkResultArray) > 0 { sinkResult := sinkResultArray[0] @@ -129,11 +129,11 @@ func TestEmitSyncWithAddSink(t *testing.T) { } } - // 验证结果集合是否一致(不考虑顺序) + // Verify whether the result set is consistent (regardless of order) assert.ElementsMatch(t, syncTemperatures, asyncTemperatures, "温度值集合应该一致") assert.ElementsMatch(t, syncHumidities, asyncHumidities, "湿度值集合应该一致") - // 验证预期的数值是否都存在 + // Verify whether the expected values are fully realized assert.Contains(t, syncTemperatures, 25.0, "同步结果应包含25.0") assert.Contains(t, syncTemperatures, 30.0, "同步结果应包含30.0") assert.Contains(t, asyncTemperatures, 25.0, "异步结果应包含25.0") @@ -145,15 +145,15 @@ func TestEmitSyncWithAddSink(t *testing.T) { ssql := streamsql.New() defer ssql.Stop() - // 执行聚合查询 + // Perform aggregated queries sql := "SELECT AVG(temperature) as avg_temp FROM stream GROUP BY TumblingWindow('1s')" err := ssql.Execute(sql) require.NoError(t, err) - // 验证是聚合查询 + // Validation is an aggregated query assert.True(t, ssql.IsAggregationQuery()) - // 尝试同步处理应该返回错误 + // Attempting synchronization should return an error data := map[string]any{"temperature": 25.0} result, err := ssql.EmitSync(data) @@ -166,12 +166,12 @@ func TestEmitSyncWithAddSink(t *testing.T) { ssql := streamsql.New() defer ssql.Stop() - // 执行非聚合查询 + // Perform non-aggregated queries sql := "SELECT temperature FROM stream" err := ssql.Execute(sql) require.NoError(t, err) - // 添加多个AddSink回调,使用原子操作确保线程安全 + // Add multiple AddSink callbacks and use atomic operations to ensure thread safety var sink1Count, sink2Count, sink3Count int32 ssql.AddSink(func(result []map[string]any) { @@ -186,16 +186,16 @@ func TestEmitSyncWithAddSink(t *testing.T) { atomic.AddInt32(&sink3Count, 1) }) - // 处理一条数据 + // Processing a single piece of data data := map[string]any{"temperature": 25.0} result, err := ssql.EmitSync(data) require.NoError(t, err) require.NotNil(t, result) - // 等待异步回调 + // Waiting for an asynchronous pullback time.Sleep(100 * time.Millisecond) - // 验证所有回调都被触发 + // Verify that all callbacks are triggered assert.Equal(t, int32(1), atomic.LoadInt32(&sink1Count)) assert.Equal(t, int32(1), atomic.LoadInt32(&sink2Count)) assert.Equal(t, int32(1), atomic.LoadInt32(&sink3Count)) @@ -205,63 +205,63 @@ func TestEmitSyncWithAddSink(t *testing.T) { ssql := streamsql.New() defer ssql.Stop() - // 执行带过滤条件的查询 + // Execute queries with filtering conditions sql := "SELECT temperature FROM stream WHERE temperature > 30" err := ssql.Execute(sql) require.NoError(t, err) - // 添加AddSink回调 + // Add AddSink callbacks var sinkCallCount int32 ssql.AddSink(func(result []map[string]any) { atomic.AddInt32(&sinkCallCount, 1) }) - // 处理不符合条件的数据 - data := map[string]any{"temperature": 20.0} // 不符合 > 30 的条件 + // Handling data that does not meet the criteria + data := map[string]any{"temperature": 20.0} // Does not meet the conditions of > 30 result, err := ssql.EmitSync(data) require.NoError(t, err) assert.Nil(t, result, "不符合过滤条件应该返回nil") - // 等待可能的异步回调 + // Waiting for possible asynchronous pullbacks time.Sleep(100 * time.Millisecond) - // 验证AddSink没有被触发 + // Verify that AddSink is not triggered assert.Equal(t, int32(0), atomic.LoadInt32(&sinkCallCount), "过滤掉的数据不应触发AddSink") }) - // 新增测试:字符串常量与反引号字段的复杂混合用法 + // New test: complex mixed usage of string constants and inquotation fields t.Run("字符串常量与反引号字段混合用法", func(t *testing.T) { ssql := streamsql.New() defer ssql.Stop() - // 测试包含多种字符串常量的SQL查询 + // The test contains SQL queries containing various string constants sql := "SELECT `temperature` as temp, 'celsius' as unit, 'high' as level, `humidity`, 'percent' as humidity_unit FROM stream WHERE temperature > 20" err := ssql.Execute(sql) require.NoError(t, err) - // 测试数据 + // Test data testData := map[string]any{ "temperature": 25.5, "humidity": 65.0, } - // 同步处理 + // Synchronized processing result, err := ssql.EmitSync(testData) require.NoError(t, err) require.NotNil(t, result) syncResult := result - // 验证反引号字段 + // Validate the backquoted field assert.Equal(t, 25.5, syncResult["temp"], "温度字段应该正确") assert.Equal(t, 65.0, syncResult["humidity"], "湿度字段应该正确") - // 验证字符串常量字段 + // Validate string constant fields assert.Equal(t, "celsius", syncResult["unit"], "单位应该是celsius") assert.Equal(t, "high", syncResult["level"], "级别应该是high") assert.Equal(t, "percent", syncResult["humidity_unit"], "湿度单位应该是percent") }) } -// TestEmitSyncPerformance 测试EmitSync性能(包括AddSink触发) +// TestEmitSyncPerformance Tests EmitSync performance (including AddSink triggers) func TestEmitSyncPerformance(t *testing.T) { t.Parallel() ssql := streamsql.New() @@ -271,13 +271,13 @@ func TestEmitSyncPerformance(t *testing.T) { err := ssql.Execute(sql) require.NoError(t, err) - // 添加AddSink回调,使用原子操作确保线程安全 + // Add AddSink callbacks and use atomic operations to ensure thread safety var sinkCallCount int32 ssql.AddSink(func(result []map[string]any) { atomic.AddInt32(&sinkCallCount, 1) }) - // 性能测试 + // Performance testing testCount := 1000 start := time.Now() @@ -293,10 +293,10 @@ func TestEmitSyncPerformance(t *testing.T) { } duration := time.Since(start) - // 等待所有异步回调完成 + // Wait for all asynchronous callbacks to complete time.Sleep(200 * time.Millisecond) - // 验证性能和一致性 + // Verify performance and consistency assert.Less(t, duration, 1*time.Second, "性能应该足够好") assert.Equal(t, int32(testCount), atomic.LoadInt32(&sinkCallCount), "所有数据都应触发AddSink") } diff --git a/test/e2e/sql_check_test.go b/test/e2e/sql_check_test.go index 4bc9a3c..8025400 100644 --- a/test/e2e/sql_check_test.go +++ b/test/e2e/sql_check_test.go @@ -7,20 +7,20 @@ import ( "github.com/stretchr/testify/assert" ) -// SQL 解析期校验的集成测试(rsql/ast.go ToStreamConfig)。 -// 汇总各类"应在解析期被拒"与"应被放行"的 SQL,锁定校验边界、防回归。 +// Integrated testing of SQL parsing period checks (rsql/ast.go ToStreamConfig). +// Aggregate various SQL items that "should be rejected during parsing" and "should be allowed to be released," lock validation boundaries, and prevent regression. // -// 校验分几类: -// 1. 分析函数不得嵌进标量函数(UPPER/ABS/ROUND...);算术表达式里带 OVER 的分析函数合法 -// (如 ts - lag(ts) OVER(...)),不在此拦;字符串字面量(单/双引号,含转义引号)里的 -// "lag(" 不得误判为函数调用。 -// 2. 窗口查询里分析函数的参数必须是聚合函数或 GROUP BY 字段,不得引用裸原始列(D1)。 -// 3. 嵌套:分析套分析、聚合套分析非法(D9);分析套聚合(内联写法)合法。 -// 4. 分析函数 alias 不得与其它输出列同名(静默覆盖=半成品,D3)。 -// 5. GROUP BY 窗口之上不得叠 OVER(...)(阈值/持续检测改用 HAVING)。 -// 6. WHERE 中的分析函数(含 OVER)由直连路径状态机求值,不受 SELECT 标量守卫约束。 +// Validation is divided into several categories: +// 1. Analysis functions must not be embedded in scalar functions (UPPER/ABS/ROUND...)); The analysis function with OVER in the arithmetic expression is valid +// (e.g., ts - lag(ts) OVER(...)), not stopped here; String literals (single/double quotes, including escape quotes). +// "lag(" must not be mistakenly identified as a function call. +// 2. In window queries, the parameters of the analysis function must be aggregate functions or GROUP BY fields; you cannot reference the raw column (D1). +// 3. Nested: Nested analysis and aggregate nest analysis are illegal (D9); Analytical set aggregation (inline writing) is legal. +// 4. The alias analysis function must not be the same name as other output columns (silent coverage = semi-finished product, D3). +// 5. Do not stack OVER on top of the GROUP BY window(...) (Threshold/continuous detection switches to HAVING). +// 6. The analysis function in WHERE (including OVER) is evaluated by the directly connected path state machine and is not constrained by SELECT scalar guards. -// assertRejectExec 期望 Execute 在解析期报错,且错误信息包含 sub。 +// assertRejectExec expects Execute to report an error at parse time, and the error message contains sub. func assertRejectExec(t *testing.T, sql, sub string) { t.Helper() ssql := streamsql.New() @@ -34,7 +34,7 @@ func assertRejectExec(t *testing.T, sql, sub string) { } } -// assertAcceptExec 期望 Execute 解析通过(不报错)。 +// assertAcceptExec expects Execute to parse successfully (no errors). func assertAcceptExec(t *testing.T, sql string) { t.Helper() assert.NoError(t, errExec(sql), "不应被解析期拒绝: %s", sql) @@ -46,19 +46,19 @@ func errExec(sql string) error { return ssql.Execute(sql) } -// --- 1. 分析函数嵌进标量函数:放行(标量套分析走 wrapper 回代,支持 coalesce/CASE/UPPER 等)--- +// --- 1. Analysis function embedding scalar functions: release (scalar sleeve analysis follows wrapper subgeneration, supports coalesce/CASE/UPPER, etc.)--- func TestSQLCheck_AnalyticInScalarFunction_Accept(t *testing.T) { for _, sql := range []string{ - `SELECT UPPER(changed_col(true, temperature)) AS c FROM stream`, // 标量套分析(小写) - `SELECT UPPER(CHANGED_COL(true, temperature)) AS c FROM stream`, // 标量套分析(大写) - `SELECT ROUND(lag(temperature), 2) AS c FROM stream`, // 标量套 lag - `SELECT ABS(acc_sum(v)) AS a FROM stream`, // 标量套 acc - `SELECT ROUND(UPPER(changed_col(true, temperature)), 2) AS c FROM stream`, // 深层嵌套标量套分析 - `SELECT CONCAT('prefix', changed_col(true, temperature)) AS s FROM stream`, // 多参标量套分析 - `SELECT LOWER(latest(temperature)) AS s FROM stream`, // 标量套 latest - `SELECT coalesce(lag(temp), -1) AS s FROM stream`, // 标量套 lag + 默认值 - `SELECT CASE WHEN lag(temp) > 20 THEN 'up' ELSE 'down' END AS s FROM stream`, // CASE 套分析 + `SELECT UPPER(changed_col(true, temperature)) AS c FROM stream`, // Scalar Set Analysis (lowercase) + `SELECT UPPER(CHANGED_COL(true, temperature)) AS c FROM stream`, // Scalar Suite Analysis (in words) + `SELECT ROUND(lag(temperature), 2) AS c FROM stream`, // Scalar set lag + `SELECT ABS(acc_sum(v)) AS a FROM stream`, // Scalar sleeve ACC + `SELECT ROUND(UPPER(changed_col(true, temperature)), 2) AS c FROM stream`, // Deep nested scalar sleeve analysis + `SELECT CONCAT('prefix', changed_col(true, temperature)) AS s FROM stream`, // Multi-parameter scalar set analysis + `SELECT LOWER(latest(temperature)) AS s FROM stream`, // Scalar application latest + `SELECT coalesce(lag(temp), -1) AS s FROM stream`, // Scalar set lag + default values + `SELECT CASE WHEN lag(temp) > 20 THEN 'up' ELSE 'down' END AS s FROM stream`, // CASE set analysis } { t.Run(sql, func(t *testing.T) { assertAcceptExec(t, sql) @@ -66,49 +66,49 @@ func TestSQLCheck_AnalyticInScalarFunction_Accept(t *testing.T) { } } -// --- 1b. 分析函数合法位置 + 字符串字面量不误伤:放行 --- +// --- 1b. Analyze the function at the valid position + string literal count without accident: Release --- func TestSQLCheck_AnalyticValidPlacement_Accept(t *testing.T) { for _, sql := range []string{ - `SELECT UPPER(name) AS n FROM stream`, // 纯标量 - `SELECT temperature * 1.8 + 32 AS f FROM stream`, // 纯算术 - `SELECT changed_col(true, temperature) AS c FROM stream`, // 顶层分析 - `SELECT lag(temperature) OVER (PARTITION BY deviceId) AS p FROM stream`, // 分析 + OVER - `SELECT acc_sum(v) AS s FROM stream`, // acc 顶层 - // acc 全族顶层合法。 + `SELECT UPPER(name) AS n FROM stream`, // Pure scalar + `SELECT temperature * 1.8 + 32 AS f FROM stream`, // Pure arithmetic + `SELECT changed_col(true, temperature) AS c FROM stream`, // Top-level analysis + `SELECT lag(temperature) OVER (PARTITION BY deviceId) AS p FROM stream`, // Analysis + OVER + `SELECT acc_sum(v) AS s FROM stream`, // acc top level + // ACC The top level of the entire clan is legal. `SELECT acc_max(v) AS m FROM stream`, `SELECT acc_min(v) AS m FROM stream`, `SELECT acc_count(v) AS c FROM stream`, `SELECT acc_avg(v) AS a FROM stream`, - // had_changed 多列变参顶层合法。 + // had_changed Multi-column variable parameter top-level valid. `SELECT had_changed(true, a, b) AS h FROM stream`, - // OVER 子句多种合法写法。 - `SELECT lag(temp, 1) OVER () AS p FROM stream`, // 空 OVER - `SELECT lag(temp, 1, 0) OVER (PARTITION BY deviceId) AS p FROM stream`, // lag 带默认值 - `SELECT acc_sum(v) OVER (PARTITION BY deviceId) AS s FROM stream`, // acc + OVER - `SELECT latest(temp) OVER (PARTITION BY deviceId) AS l FROM stream`, // latest + OVER - `SELECT lag(temp) OVER (PARTITION BY deviceId WHEN temp > 0) AS p FROM stream`, // OVER(WHEN) 输入门控 - // 算术表达式里带 OVER 的分析函数:合法(标量套分析检查仅拦"标量函数"顶层,不拦算术)。 + // The OVER clause has multiple legal writing methods. + `SELECT lag(temp, 1) OVER () AS p FROM stream`, // Empty OVER + `SELECT lag(temp, 1, 0) OVER (PARTITION BY deviceId) AS p FROM stream`, // lag with default values + `SELECT acc_sum(v) OVER (PARTITION BY deviceId) AS s FROM stream`, // acc + OVER + `SELECT latest(temp) OVER (PARTITION BY deviceId) AS l FROM stream`, // latest + OVER + `SELECT lag(temp) OVER (PARTITION BY deviceId WHEN temp > 0) AS p FROM stream`, // OVER(WHEN) Input gating + // Analysis functions with OVER in arithmetic expressions: valid (scalar suite analysis checks only block the top level of the "scalar function", not arithmetic). `SELECT ts - lag(ts) OVER (PARTITION BY k) AS d FROM stream`, - `SELECT 100 - lag(ts) OVER (PARTITION BY k) AS d FROM stream`, // 分析函数在算术右侧 - // 多个分析字段、分析字段与普通列/标量共存:合法。 + `SELECT 100 - lag(ts) OVER (PARTITION BY k) AS d FROM stream`, // The analysis function is on the right side of arithmetic + // Multiple analysis fields, analysis fields coexisting with regular columns/scalars: legal. `SELECT lag(a) OVER (PARTITION BY k) AS p, changed_col(true, b) AS c FROM stream`, `SELECT lag(a) OVER (PARTITION BY k) AS p, name AS n FROM stream`, `SELECT lag(a) OVER (PARTITION BY k) AS p, UPPER(name) AS u FROM stream`, - // 字符串字面量里形如 "分析函数名(" 的文本不得误判为函数调用(曾误伤)。 - `SELECT CONCAT('lag is great', name) AS s FROM stream`, // 字面量含分析名但无 "(" - `SELECT CONCAT('see lag(', name) AS s FROM stream`, // 单引号字面量含 "lag(" - `SELECT UPPER('changed_col(x)') AS s FROM stream`, // 单引号字面量含 "changed_col(" - `SELECT UPPER('latest(y)') AS s FROM stream`, // 单引号字面量含 "latest(" - `SELECT UPPER('acc_sum(z)') AS s FROM stream`, // 单引号字面量含 "acc_sum(" - `SELECT UPPER('had_changed(w)') AS s FROM stream`, // 单引号字面量含 "had_changed(" - // 转义单引号('')字面量里的分析名不得误判:字面量边界靠转义引号,naive 剥离会漏。 - `SELECT UPPER('it''s lag(x)') AS s FROM stream`, // 转义引号后字面量内含 "lag(" - `SELECT UPPER('a''b''changed_col(x)') AS s FROM stream`, // 多个转义引号 - // 双引号字面量(changed_cols 前缀用双引号)里的分析名同样不得误判。 - `SELECT UPPER("lag(x)") AS s FROM stream`, // 双引号字面量含 "lag(" - `SELECT UPPER("changed_col(x)") AS s FROM stream`, // 双引号字面量含 "changed_col(" - `SELECT CONCAT("t_lag_", name) AS s FROM stream`, // 双引号前缀,无分析名 + // Text in string literals like "Analysis function_name(" must not be mistakenly interpreted as a function call (has been mistakenly damaged). + `SELECT CONCAT('lag is great', name) AS s FROM stream`, // The literal quantity contains the analysis name but does not have "(" + `SELECT CONCAT('see lag(', name) AS s FROM stream`, // Single quote literal numbers containing "lag(" + `SELECT UPPER('changed_col(x)') AS s FROM stream`, // Single quotation letters containing "changed_col(" + `SELECT UPPER('latest(y)') AS s FROM stream`, // Single quotation literals containing "latest(" + `SELECT UPPER('acc_sum(z)') AS s FROM stream`, // Single quotation literal numbers containing "acc_sum(" + `SELECT UPPER('had_changed(w)') AS s FROM stream`, // Single quotation letters containing "had_changed(" + // Escape single quotation marks ('') The analysis name in literal quantity must not be misinterpreted: the boundary of literal quantity is based on escape quotes; naive stripping will cause leakage. + `SELECT UPPER('it''s lag(x)') AS s FROM stream`, // The extremity after escape quotes contains "lag(") + `SELECT UPPER('a''b''changed_col(x)') AS s FROM stream`, // Multiple escape quotation marks + // The analysis name in double quotation literals (changed_cols prefix in double quotes) must also not be misinterpreted. + `SELECT UPPER("lag(x)") AS s FROM stream`, // Double quotation letters containing "lag(" + `SELECT UPPER("changed_col(x)") AS s FROM stream`, // Double quotation literal values containing "changed_col(" + `SELECT CONCAT("t_lag_", name) AS s FROM stream`, // Double quotation prefix, no analytic name } { t.Run(sql, func(t *testing.T) { assertAcceptExec(t, sql) @@ -116,22 +116,22 @@ func TestSQLCheck_AnalyticValidPlacement_Accept(t *testing.T) { } } -// --- 2. 窗口查询里的分析函数 --- +// --- 2. Analysis functions in window queries --- func TestSQLCheck_WindowAnalytic_Accept(t *testing.T) { - // 参数为聚合函数或 GROUP BY 字段:合法。 + // Parameters are aggregate function or GROUP BY field: Valid. for _, sql := range []string{ `SELECT changed_col(true, avg(temperature)) AS c FROM stream GROUP BY CountingWindow(2)`, `SELECT changed_cols("t", true, avg(temperature), max(temperature)) FROM stream GROUP BY CountingWindow(2)`, `SELECT deviceId, changed_col(true, avg(temp)) AS chg FROM stream GROUP BY deviceId, CountingWindow(2)`, `SELECT acc_sum(avg(temperature)) AS total FROM stream GROUP BY CountingWindow(2)`, - // 分析套不同聚合:合法(分析函数对窗口聚合输出求值)。 + // Analysis set different aggregations: Valid (parser function evaluates the window aggregation output). `SELECT changed_col(true, max(temp)) AS c FROM stream GROUP BY CountingWindow(2)`, `SELECT acc_sum(max(temp)) AS s FROM stream GROUP BY CountingWindow(2)`, `SELECT had_changed(true, avg(temp)) AS h FROM stream GROUP BY CountingWindow(2)`, - // 限定列恰好是 GROUP BY 键:取列名后缀判等,合法(防误伤限定列)。 + // The restricted column happens to be the GROUP BY key: select column name suffix and judge, etc., which is valid (to prevent accidental damage to restricted columns). `SELECT changed_col(true, stream.deviceId) AS c FROM stream GROUP BY deviceId, CountingWindow(2)`, - // 复杂表达式参数(含运算符)不在裸列拦截范围:合法。 + // Complex expression parameters (including operators) are not within the bare list interception range: Valid. `SELECT changed_col(true, avg(temp) + 1) AS c FROM stream GROUP BY CountingWindow(2)`, } { t.Run(sql, func(t *testing.T) { @@ -141,11 +141,11 @@ func TestSQLCheck_WindowAnalytic_Accept(t *testing.T) { } func TestSQLCheck_WindowAnalytic_RawColumn_Reject(t *testing.T) { - // D1:窗口查询里分析函数引用裸原始列(未聚合、非分组键)→ 解析期拒绝。 + // D1: In window queries, the parser function references the raw columns (not aggregated, non-grouped keys) → rejects during parsing. for _, sql := range []string{ `SELECT lag(temperature) AS p FROM stream GROUP BY CountingWindow(2)`, `SELECT deviceId, lag(temperature) AS p FROM stream GROUP BY deviceId, CountingWindow(2)`, - // 限定列但后缀名非 GROUP BY 键:同样视为裸原始列(防误伤:限定不等于豁免)。 + // Restricted columns but suffix names not GROUP BY keys: Also treated as bare original columns (Accidental damage prevention: restricted does not equal exemption). `SELECT changed_col(true, stream.deviceId) AS c FROM stream GROUP BY CountingWindow(2)`, } { t.Run(sql, func(t *testing.T) { @@ -154,24 +154,24 @@ func TestSQLCheck_WindowAnalytic_RawColumn_Reject(t *testing.T) { } } -// --- 3. 嵌套函数(D9)--- +// --- 3. Nested Functions (D9)--- func TestSQLCheck_NestedAnalytic_Reject(t *testing.T) { - // 分析套分析、聚合套分析 → 非法。 + // Analytical suite analysis, aggregate suite analysis → illegal. for _, sql := range []string{ - `SELECT lag(lag(a)) AS p FROM stream`, // 分析套分析 - `SELECT had_changed(true, changed_col(true, a)) AS h FROM stream`, // 分析套分析 - `SELECT sum(lag(a)) AS s FROM stream GROUP BY CountingWindow(2)`, // 聚合套分析(窗口) - `SELECT max(acc_sum(v)) AS m FROM stream GROUP BY CountingWindow(2)`, // 聚合套 acc + `SELECT lag(lag(a)) AS p FROM stream`, // Analysis set + `SELECT had_changed(true, changed_col(true, a)) AS h FROM stream`, // Analysis set + `SELECT sum(lag(a)) AS s FROM stream GROUP BY CountingWindow(2)`, // Aggregate Suite Analysis (Window) + `SELECT max(acc_sum(v)) AS m FROM stream GROUP BY CountingWindow(2)`, // Polymerization sleeve ACC } { t.Run(sql, func(t *testing.T) { - assertRejectExec(t, sql, "") // 各路径错误信息不同,只断言报错 + assertRejectExec(t, sql, "") // Each path has different error messages, only assertioning errors }) } } func TestSQLCheck_AnalyticWrappingAggregate_Accept(t *testing.T) { - // 分析套聚合(内联写法)→ 合法。 + // Analytical set aggregation (inline writing) → Legal. for _, sql := range []string{ `SELECT changed_cols("t", true, avg(temperature)) FROM stream GROUP BY CountingWindow(2)`, `SELECT lag(avg(temperature)) AS p FROM stream GROUP BY CountingWindow(2)`, @@ -182,19 +182,19 @@ func TestSQLCheck_AnalyticWrappingAggregate_Accept(t *testing.T) { } } -// 嵌套错误信息锁定:各非法嵌套路径的报错文案不同,逐条钉死防回归。 +// Nested error message locking: Each illegal nested path has different error messages, pinned one by one to prevent regression. func TestSQLCheck_NestedAnalytic_ErrorMessages_Reject(t *testing.T) { for _, c := range []struct { sql string sub string }{ - {`SELECT lag(lag(a)) AS p FROM stream`, "analytic functions cannot be nested"}, // 分析套分析 - {`SELECT changed_col(true, lag(a)) AS c FROM stream`, "analytic functions cannot be nested"}, // 分析套分析(反向) - {`SELECT lag(had_changed(true, a)) AS p FROM stream`, "analytic functions cannot be nested in had_changed"}, // 分析套分析(具名) - {`SELECT sum(lag(a)) AS s FROM stream GROUP BY CountingWindow(2)`, "analytic functions cannot be nested"}, // 聚合套分析 - {`SELECT sum(count(x)) AS s FROM stream GROUP BY CountingWindow(2)`, "aggregate function calls cannot be nested"}, // 聚合套聚合 - {`SELECT max(sum(x)) AS m FROM stream GROUP BY CountingWindow(2)`, "aggregate function calls cannot be nested"}, // 聚合套聚合 + {`SELECT lag(lag(a)) AS p FROM stream`, "analytic functions cannot be nested"}, // Analysis set + {`SELECT changed_col(true, lag(a)) AS c FROM stream`, "analytic functions cannot be nested"}, // Analysis Set Analysis (Reverse Direction) + {`SELECT lag(had_changed(true, a)) AS p FROM stream`, "analytic functions cannot be nested in had_changed"}, // Analysis Suite Analysis (Named) + {`SELECT sum(lag(a)) AS s FROM stream GROUP BY CountingWindow(2)`, "analytic functions cannot be nested"}, // Polymerization set analysis + {`SELECT sum(count(x)) AS s FROM stream GROUP BY CountingWindow(2)`, "aggregate function calls cannot be nested"}, // Polymerization sets + {`SELECT max(sum(x)) AS m FROM stream GROUP BY CountingWindow(2)`, "aggregate function calls cannot be nested"}, // Polymerization sets } { t.Run(c.sql, func(t *testing.T) { assertRejectExec(t, c.sql, c.sub) @@ -202,14 +202,14 @@ func TestSQLCheck_NestedAnalytic_ErrorMessages_Reject(t *testing.T) { } } -// --- 4. 分析函数 alias 与其它输出列同名(D3)--- +// --- 4. The analysis function alias shares the same name as other output columns (D3)--- func TestSQLCheck_AnalyticAliasCollision_Reject(t *testing.T) { - // 分析函数 alias 与普通列/其它分析 alias 同名 → 静默覆盖,解析期拒绝。 + // The analysis function alias shares the same name as the regular column/other analysis alias → muted coverage, rejected during the parsing period. for _, sql := range []string{ - `SELECT temperature, lag(temperature) AS temperature FROM stream`, // alias 撞普通列 - `SELECT lag(a) AS x, changed_col(true, a) AS x FROM stream`, // 两个分析 alias 撞 - `SELECT lag(a) AS k, had_changed(true, a) AS k FROM stream`, // 两个分析 alias 撞 + `SELECT temperature, lag(temperature) AS temperature FROM stream`, // Alias crashed into a regular line + `SELECT lag(a) AS x, changed_col(true, a) AS x FROM stream`, // Two analyses of alias collide + `SELECT lag(a) AS k, had_changed(true, a) AS k FROM stream`, // Two analyses of alias collide } { t.Run(sql, func(t *testing.T) { assertRejectExec(t, sql, "duplicate output column") @@ -218,7 +218,7 @@ func TestSQLCheck_AnalyticAliasCollision_Reject(t *testing.T) { } func TestSQLCheck_AnalyticAliasDistinct_Accept(t *testing.T) { - // alias 各异(含与普通列区分)→ 合法(防误伤)。 + // Alias vary (including distinction from regular columns) → Legal (to prevent accidental damage). for _, sql := range []string{ `SELECT temperature, lag(temperature) AS temp_lag FROM stream`, `SELECT lag(a) AS la, changed_col(true, a) AS ca FROM stream`, @@ -229,11 +229,11 @@ func TestSQLCheck_AnalyticAliasDistinct_Accept(t *testing.T) { } } -// --- 5. GROUP BY 窗口之上不得叠 OVER(...)(阈值/持续检测用 HAVING)--- +// --- 5. GROUP BY cannot be stacked above the window OVER(...) (HAVING for threshold/continuous detection)--- func TestSQLCheck_OverOnWindow_Reject(t *testing.T) { - // parser 接受 GROUP BY OVER(...) 语法并存入 Window.Over, - // ToStreamConfig 显式拒绝并指引改用 HAVING(parseGroupBy 错误会被 errorRecovery 吞,故延后拒)。 + // parser accepts the GROUP BY OVER(...) syntax and stores it in Window.Over, + // ToStreamConfig explicitly rejects and guides to switch to HAVING (parseGroupBy errors are swallowed by errorRecovery, so postponed rejection). for _, sql := range []string{ `SELECT avg(temp) AS m FROM stream GROUP BY CountingWindow(2) OVER (WHEN x > 0)`, `SELECT deviceId, max(temp) AS m FROM stream GROUP BY deviceId, CountingWindow(2) OVER (WHEN x > 0)`, @@ -244,7 +244,7 @@ func TestSQLCheck_OverOnWindow_Reject(t *testing.T) { } } -// HAVING 是窗口阈值/持续检测的正规写法:合法(防误伤——别把替代方案也拦了)。 +// HAVING is the standard way to use window threshold/continuous detection: legal (to prevent accidental damage—don't block alternative solutions too). func TestSQLCheck_HavingOnWindow_Accept(t *testing.T) { for _, sql := range []string{ @@ -256,10 +256,10 @@ func TestSQLCheck_HavingOnWindow_Accept(t *testing.T) { } } -// --- 6. WHERE 中的分析函数(SELECT 标量守卫不得波及 WHERE)--- +// --- 6. Analysis function in WHERE (SELECT scalar guards must not affect WHERE)--- func TestSQLCheck_WhereAnalytic_Accept(t *testing.T) { - // WHERE 里的分析函数(含 OVER)走直连路径状态机求值,不受"标量套分析"守卫约束。 + // The analysis functions (including OVER) in WHERE are directly connected to the path state machine and are not constrained by the "scalar suite analysis" guard. for _, sql := range []string{ `SELECT * FROM stream WHERE changed_col(true, temp) > 0`, `SELECT temp FROM stream WHERE changed_col(true, temp)`, @@ -271,7 +271,7 @@ func TestSQLCheck_WhereAnalytic_Accept(t *testing.T) { } } -// OVER 子句只支持 PARTITION BY / WHEN,不支持 ORDER BY(无 frame):解析期即报错。 +// The OVER clause only supports PARTITION BY / WHEN, does not support ORDER BY (FRAME): an error occurs during the parsing period. func TestSQLCheck_OrderByInOver_Reject(t *testing.T) { assertRejectExec(t, `SELECT lag(temp) OVER (PARTITION BY deviceId ORDER BY ts) AS p FROM stream`, "") diff --git a/test/e2e/stress_test.go b/test/e2e/stress_test.go index 707eadb..a94d4de 100644 --- a/test/e2e/stress_test.go +++ b/test/e2e/stress_test.go @@ -10,12 +10,12 @@ import ( "github.com/stretchr/testify/require" ) -// 压力测试:v1.0.3 分析函数路径在持续负载下的稳定性——goroutine 不泄漏、堆不无限增长、 -// 无 panic。针对 v1.0.2→v1.0.3(OVER 状态机 / PARTITION BY / splitAnalyticExprMulti)最可能 -// 引入的回归。本地普通模式跑(-race 由 CI/Linux 回归)。 +// Stress Testing: v1.0.3 Analyze the stability of function paths under continuous load—goroutine does not leak, heaps do not grow infinitely, +// No panic. Most likely for v1.0.2→v1.0.3 (OVER State Machine / PARTITION BY / splitAnalyticExprMulti). +// Introducing a return. Local normal mode running (-race reverts from CI/Linux). -// 多次 New→负载→Stop 循环:每次 Stop 应回收全部 sink/处理协程,NumGoroutine 回到基线。 -// 捕获“Stop 不收敛”型泄漏——每轮残留 k 个 → 末值 = base + cycles*k,远超基线。 +// Multiple New → load→Stop cycle: Each Stop should recover all sink/processing coroutines, and NumGoroutine returns to baseline. +// Capture "Stop non-convergence" type leaks—each round leaves k → final values = base + cycles * k, far exceeding the baseline. func TestStress_NoGoroutineLeak_CreateStop(t *testing.T) { runtime.GC() base := runtime.NumGoroutine() @@ -32,7 +32,7 @@ func TestStress_NoGoroutineLeak_CreateStop(t *testing.T) { s.Stop() } - // Stop 为 grace join,给余量等残留协程退出后采样。 + // Stop is grace join, which samples residual coroutines such as margins after exiting. deadline := time.Now().Add(3 * time.Second) var final int for { @@ -51,15 +51,15 @@ func TestStress_NoGoroutineLeak_CreateStop(t *testing.T) { t.Logf("goroutine: base=%d final=%d (cycles=%d)", base, final, cycles) } -// 单实例持续 10 万事件(50 分区,落在默认上限内):堆增量受控、全程无 panic。 -// 同时给出持续吞吐作为性能参考。堆增量若超阈值,疑为每事件或每分区状态留存型泄漏。 +// A single instance sustains 100,000 events (50 partitions, within the default limit): heap increment is controlled with no panic throughout. +// At the same time, continuous throughput is provided as a performance reference. If the heap increment exceeds the threshold, it is suspected to be a per-event or per-partition state-retention leakage. func TestStress_SustainedLoad_HeapStable(t *testing.T) { s := streamsql.New() require.NoError(t, s.Execute( `SELECT deviceId, lag(v) OVER (PARTITION BY deviceId) AS p, acc_sum(v) OVER (PARTITION BY deviceId) AS t FROM stream`)) defer s.Stop() - // 预热(触发 ensureAnalytic 的 sync.Once 初始化),再取基线。 + // Preheat (trigger ensureAnalytic sync.Once initialization), then take the baseline. _, err := s.EmitSync(map[string]any{"deviceId": 0, "v": 0}) require.NoError(t, err) runtime.GC() @@ -79,13 +79,13 @@ func TestStress_SustainedLoad_HeapStable(t *testing.T) { runtime.ReadMemStats(&ms) heapEnd := ms.HeapAlloc - t.Logf("持续负载: %d 事件 / %v = %.0f ops/sec", events, dur, float64(events)/dur.Seconds()) - t.Logf("堆: %.2fMB → %.2fMB (delta %.2fMB)", float64(heapStart)/1e6, float64(heapEnd)/1e6, float64(int64(heapEnd) - int64(heapStart))/1e6) - require.Less(t, float64(int64(heapEnd) - int64(heapStart)), 50.0*1e6, - "堆增量过大,疑为状态留存型泄漏:delta=%.2fMB", float64(int64(heapEnd) - int64(heapStart))/1e6) + t.Logf("Continuous load: %d events / %v = %.0f ops/sec", events, dur, float64(events)/dur.Seconds()) + t.Logf("Dui: %.2fMB → %.2fMB (delta %.2fMB)", float64(heapStart)/1e6, float64(heapEnd)/1e6, float64(int64(heapEnd)-int64(heapStart))/1e6) + require.Less(t, float64(int64(heapEnd)-int64(heapStart)), 50.0*1e6, + "堆增量过大,疑为状态留存型泄漏:delta=%.2fMB", float64(int64(heapEnd)-int64(heapStart))/1e6) } -// 分区数远超默认上限:LRU 驱逐在持续负载下不应泄漏、不应 panic。每轮把最久未用分区淘汰。 +// The number of partitions far exceeds the default limit: LRU ejection should not leak or panic under continuous load. Each round, the longest group eliminated without a division. func TestStress_PartitionEviction_NoLeak(t *testing.T) { s := streamsql.New() require.NoError(t, s.Execute( @@ -99,7 +99,7 @@ func TestStress_PartitionEviction_NoLeak(t *testing.T) { runtime.ReadMemStats(&ms) heapStart := ms.HeapAlloc - // 5 万个不同分区,持续触发淘汰;每分区仅一条,lag 恒为 nil。 + // 50,000 different zones, continuously triggering elimination; Each partition has only one segment, and lag is always nil. const distinct = 50000 start := time.Now() for i := 0; i < distinct; i++ { @@ -111,17 +111,17 @@ func TestStress_PartitionEviction_NoLeak(t *testing.T) { runtime.ReadMemStats(&ms) heapEnd := ms.HeapAlloc - t.Logf("淘汰负载: %d 分区 / %v = %.0f ops/sec", distinct, dur, float64(distinct)/dur.Seconds()) - t.Logf("堆: %.2fMB → %.2fMB (delta %.2fMB)", float64(heapStart)/1e6, float64(heapEnd)/1e6, float64(int64(heapEnd) - int64(heapStart))/1e6) - // 驱逐应把旧分区回收,驻留仅近 LRU 上限个;驻留若随总分区数线性增长即为驱逐失效。 - require.Less(t, float64(int64(heapEnd) - int64(heapStart)), 100.0*1e6, - "堆随分区数线性增长,疑为 LRU 驱逐未回收:delta=%.2fMB", float64(int64(heapEnd) - int64(heapStart))/1e6) + t.Logf("Elimination load: %d partition / %v = %.0f ops/sec", distinct, dur, float64(distinct)/dur.Seconds()) + t.Logf("Dui: %.2fMB → %.2fMB (delta %.2fMB)", float64(heapStart)/1e6, float64(heapEnd)/1e6, float64(int64(heapEnd)-int64(heapStart))/1e6) + // Expulsion should reclaim old partitions, and only reside near the LRU limit; If the residency increases linearly with the total number of partitions, it is considered a deportation failure. + require.Less(t, float64(int64(heapEnd)-int64(heapStart)), 100.0*1e6, + "堆随分区数线性增长,疑为 LRU 驱逐未回收:delta=%.2fMB", float64(int64(heapEnd)-int64(heapStart))/1e6) } -// --- 128MB 网关常见规则容量基准 --- -// 每事件新建 map(贴近真实接入:网关把每条入消息解析成 map);单流 EmitSync。 -// 用 GOMAXPROCS / GOMEMLIMIT 环境变量模拟网关 CPU/内存约束。 -// 注意:EmitSync 是同步单 goroutine,单条规则吞吐与核数无关——多核靠并行多实例。 +// --- Common Rules and Capacity Benchmarks for 128MB Gateways --- +// Create a new map for each event (close to real access: the gateway parses each incoming message into a map); Single-stream EmitSync. +// Use GOMAXPROCS / GOMEMLIMIT environment variables to simulate the gateway CPU/memory constraints. +// Note: EmitSync is a synchronous single goroutine; single rule throughput is independent of core count—multi-core relies on parallel multi-instance operation. const gatewayDeviceCount = 100 @@ -158,23 +158,23 @@ func benchGatewayRule(b *testing.B, sql string) { } } -// 常见规则1:过滤(最高频)。 +// Common rule 1: Filter (highest frequency). func BenchmarkGateway_Filter(b *testing.B) { benchGatewayRule(b, `SELECT deviceId, temperature FROM stream WHERE temperature > 25`) } -// 常见规则2:转换(单位换算)。 +// Common Rule 2: Conversion (unit conversion). func BenchmarkGateway_Transform(b *testing.B) { benchGatewayRule(b, `SELECT deviceId, temperature * 1.8 + 32 AS fahrenheit FROM stream`) } -// 常见规则3:变化检测(分析函数 + 分区)。 +// Common Rule 3: Change detection (analysis function + partitioning). func BenchmarkGateway_AnalyticChange(b *testing.B) { benchGatewayRule(b, `SELECT deviceId, temperature, lag(temperature) OVER (PARTITION BY deviceId) AS prev FROM stream`) } -// 并行多实例(多核利用:网关同跑多条规则,每核一条)。RunParallel 随 GOMAXPROCS 扩展, -// 体现多核网关的聚合吞吐。 +// Parallel multi-instance (multi-core utilization: gateways run multiple rules simultaneously, one per core). RunParallel extends with GOMAXPROCS, +// Demonstrates the aggregation throughput of multi-core gateways. func BenchmarkGateway_ParallelInstances(b *testing.B) { sql := `SELECT deviceId, temperature FROM stream WHERE temperature > 25` b.ReportAllocs() @@ -192,10 +192,10 @@ func BenchmarkGateway_ParallelInstances(b *testing.B) { }) } -// TestWindowEventTime_MultiTimestampAggregation 验证 event-time TumblingWindow 的 epoch 对齐: -// 1s 窗口对齐到整秒边界(alignWindowStart),多时间戳事件须落在同一对齐窗口内才会一起聚合。 -// base 对齐到秒边界后,base / base+100 同属 [base, base+1000),count 恒为 2。 -// (曾误判为「窗口竞态」——实为测试时间戳跨越 epoch 对齐边界所致,引擎无误。) +// TestWindowEventTime_MultiTimestampAggregation Verify epoch alignment of the event-time TumblingWindow: +// 1s window alignment to the full-second boundary (alignWindowStart); multi-timestamp events must fall within the same aligned window to aggregate together. +// After aligning base to the second boundary, base / base+100 belong to [base, base+1000), and count is always 2. +// (It was once misidentified as "window race"—actually caused by test timestamps crossing epoch alignment boundaries, but the engine was correct.)) func TestWindowEventTime_MultiTimestampAggregation(t *testing.T) { const iters = 300 fails := 0 @@ -206,7 +206,7 @@ func TestWindowEventTime_MultiTimestampAggregation(t *testing.T) { } ch := make(chan []map[string]any, 4) s.AddSink(func(r []map[string]any) { ch <- r }) - base := ((time.Now().UnixMilli() - 5000) / 1000) * 1000 // 对齐到秒边界:避免 base/base+100 跨越 epoch 对齐的 1s 窗口 + base := ((time.Now().UnixMilli() - 5000) / 1000) * 1000 // Align to the second boundary: Avoid base/base+100 crossing the 1-second window of epoch alignment s.Emit(map[string]any{"ts": base, "v": 10}) s.Emit(map[string]any{"ts": base + 100, "v": 60}) s.Emit(map[string]any{"ts": base + 2000, "v": 5}) diff --git a/test/e2e/trigger_window_test.go b/test/e2e/trigger_window_test.go index ee2048a..6d9cac3 100644 --- a/test/e2e/trigger_window_test.go +++ b/test/e2e/trigger_window_test.go @@ -8,9 +8,9 @@ import ( "github.com/rulego/streamsql" ) -// TestTriggerWindow 验证 TriggerWindow 手动触发窗口立即输出(不等自然触发)。 -// TumblingWindow('5s') ProcessingTime:Trigger 提前触发当前窗口(不等满 5s)。 -// 注:CountingWindow.Trigger 为空实现(按 count 触发,设计如此),故用 TumblingWindow 验证。 +// TestTriggerWindow verifies TriggerWindow. Manually triggered window outputs immediately (not necessarily triggered naturally). +// TumblingWindow('5s') ProcessingTime: Trigger Triggers the current window early (not until 5 seconds). +// Note: CountingWindow.Trigger is an empty implementation (triggered by count, designed accordingly), so TumblingWindow is used for verification. func TestTriggerWindow(t *testing.T) { t.Parallel() ssql := streamsql.New() @@ -52,7 +52,7 @@ func TestTriggerWindow(t *testing.T) { } } -// TestTriggerWindowNoWindow 验证非窗口(直接路径)查询调 TriggerWindow 不 panic(无窗口可触发)。 +// TestTriggerWindowNoWindow verifies a non-window (direct path) query calls TriggerWindow not panic (no window can be triggered). func TestTriggerWindowNoWindow(t *testing.T) { t.Parallel() ssql := streamsql.New() diff --git a/test/e2e/validation_test.go b/test/e2e/validation_test.go index 19c6c4b..ba950a2 100644 --- a/test/e2e/validation_test.go +++ b/test/e2e/validation_test.go @@ -54,7 +54,7 @@ func TestFunctionValidationIntegration(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { ssql := streamsql.New() - defer ssql.Stop() + defer ssql.Stop() err := ssql.Execute(tt.sql) if tt.expectError { @@ -71,10 +71,10 @@ func TestFunctionValidationIntegration(t *testing.T) { func TestFunctionValidationWithCustomFunctions(t *testing.T) { t.Parallel() - // 测试自定义函数注册后的验证 + // Test the validation after custom function registration sql := "SELECT custom_func(temperature) FROM stream" - // 在没有注册自定义函数时应该报错 + // An error should occur when no custom function is registered ssql := streamsql.New() defer ssql.Stop() err := ssql.Execute(sql) diff --git a/test/e2e/window_aggregate_combo_test.go b/test/e2e/window_aggregate_combo_test.go index 5cb7914..dc0863b 100644 --- a/test/e2e/window_aggregate_combo_test.go +++ b/test/e2e/window_aggregate_combo_test.go @@ -9,11 +9,11 @@ import ( "github.com/stretchr/testify/require" ) -// 窗口 × 聚合组合的语义测试。预期值从 SQL 标准语义推导(不从代码反推)。 -// 统一用事件时间 + 远未来事件推水位触发窗口(确定性,不依赖真实时钟)。 +// Window × Semantic testing of aggregated combinations. Expected values are derived from SQL standard semantics (not backwards from code). +// Unified trigger window using event time + distant future event push-up threshold (deterministic, not dependent on the actual clock). -// asFloat64 把 count/sum/avg 等可能为 int/int64/float64 的聚合结果统一成 float64 比对, -// 避免 count(*) 的具体整数类型耦合。 +// asFloat64 unifies the aggregate results of count/sum/avg that may be int/int/int64/float64 into float64 comparison, +// Avoid coupling count(*) into specific integer types. func asFloat64(v any) float64 { switch x := v.(type) { case float64: @@ -30,8 +30,8 @@ func asFloat64(v any) float64 { return 0 } -// HAVING 引用「未在 SELECT 出现」的聚合:SELECT 只有 count(*),HAVING 用 max(v)。 -// 窗口 [base,base+1s) 值 10,60 → max=60>50 通过,count=2。 +// HAVING references aggregations that do not appear in SELECT: SELECT only count(*), HAVING uses max(v). +// Window [base,base+1s) values 10, 60 → max=60>50 pass, count=2. func TestWindowCombo_Having_NonSelectedAggregate(t *testing.T) { ssql := streamsql.New() defer ssql.Stop() @@ -43,19 +43,19 @@ func TestWindowCombo_Having_NonSelectedAggregate(t *testing.T) { base := time.Now().UnixMilli() - 5000 ssql.Emit(map[string]any{"ts": base, "v": 10.0}) ssql.Emit(map[string]any{"ts": base, "v": 60.0}) - ssql.Emit(map[string]any{"ts": base + 2000, "v": 5.0}) // 推水位触发 [base,base+1s) + ssql.Emit(map[string]any{"ts": base + 2000, "v": 5.0}) // Push the water level to trigger [base,base+1s) select { case rows := <-ch: require.Len(t, rows, 1) assert.Equal(t, 2.0, asFloat64(rows[0]["c"]), "count=2,max=60>50 应通过 HAVING") case <-time.After(5 * time.Second): - t.Fatal("timeout: HAVING 引用未选中的 max(v) 应能正常求值触发") + t.Fatal("timeout: HAVING The reference to an unselected max(v) should be able to evaluate and trigger normally") } } -// 两个聚合做算术(后聚合回代):SELECT max(v)-min(v) AS rng, sum(v)。 -// 值 10,40,25 → max-min=30,sum=75。 +// Arithmetic for two aggregates (post-aggregation backsubstitution): SELECT max(v) - min(v) AS rng, sum(v). +// Values 10, 40, 25 → max-min=30, sum=75. func TestWindowCombo_PostAggArithmetic_TwoAggregates(t *testing.T) { ssql := streamsql.New() defer ssql.Stop() @@ -76,12 +76,12 @@ func TestWindowCombo_PostAggArithmetic_TwoAggregates(t *testing.T) { assert.Equal(t, 30.0, asFloat64(rows[0]["rng"]), "max-min = 40-10") assert.Equal(t, 75.0, asFloat64(rows[0]["total"]), "sum = 10+40+25") case <-time.After(5 * time.Second): - t.Fatal("timeout: 两聚合 max-min 算术应回代求值") + t.Fatal("timeout: Two aggregates max-min arithmetic should be performed by substitution") } } -// 同一窗口多种聚合类型:count/sum/avg/min/max 各自正确。 -// 值 10,20,30 → c=3 s=60 a=20 mn=10 mx=30。 +// Multiple aggregation types in the same window: count/sum/avg/min/max are each correct. +// Values 10, 20, 30 → c = 3, s = 60, a = 20, mn = 10, mx = 30. func TestWindowCombo_MultipleAggregates(t *testing.T) { ssql := streamsql.New() defer ssql.Stop() @@ -109,8 +109,8 @@ func TestWindowCombo_MultipleAggregates(t *testing.T) { } } -// NULL 在聚合里的处理(SQL 标准):count(*) 计行,count(v)/avg/sum 忽略 NULL。 -// 值 10,nil,30 → c=3 cv=2 a=20 s=40。 +// NULL processing in aggregates (SQL standard): count(*) counts rows, count(v)/avg/sum ignores NULL. +// Values 10, nil, 30 → c=3, cv=2, a=20, s=40. func TestWindowCombo_NullInAggregates(t *testing.T) { ssql := streamsql.New() defer ssql.Stop() diff --git a/test/e2e/window_counting_test.go b/test/e2e/window_counting_test.go index cb874c3..a074a11 100644 --- a/test/e2e/window_counting_test.go +++ b/test/e2e/window_counting_test.go @@ -68,7 +68,7 @@ func TestSQLCountingWindow_GroupedCounting_MixedDevices(t *testing.T) { ssql.AddSink(func(results []map[string]any) { defer func() { if r := recover(); r != nil { - // channel 已关闭,忽略错误 + // channel is closed, ignoring errors } }() ch <- results @@ -114,7 +114,7 @@ func TestSQLCountingWindow_MultiKeyGroupedCounting(t *testing.T) { ssql.AddSink(func(results []map[string]any) { defer func() { if r := recover(); r != nil { - // channel 已关闭,忽略错误 + // channel is closed, ignoring errors } }() ch <- results diff --git a/test/e2e/window_func_support_test.go b/test/e2e/window_func_support_test.go index 4bdbb6d..902fa56 100644 --- a/test/e2e/window_func_support_test.go +++ b/test/e2e/window_func_support_test.go @@ -9,13 +9,13 @@ import ( "github.com/stretchr/testify/require" ) -// TestWindowHavingSustainedDetection 验证主流写法:窗口聚合全部事件,HAVING 拦 dip。 -// 全 >5 的窗口通过 HAVING;含 dip(<5) 的窗口被 HAVING 拦住无输出。 -// 注意:streamsql 的 HAVING 引用 SELECT 别名(mn),不复述聚合函数。 +// TestWindowHavingSustainedDetection verifies the mainstream approach: aggregate all events in the window, HAVING to block dip. +// All >5 windows are HAVING; Window containing dip(<5) is blocked by HAVING and has no output. +// Note: HAVING in streamsql references the SELECT alias (mn) and does not restate the aggregation function. func TestWindowHavingSustainedDetection(t *testing.T) { t.Parallel() - // 窗口全 >5 → 通过 HAVING,输出 mn=9。 + // The window is >5 → HAVING, output mn=9. s1 := streamsql.New() require.NoError(t, s1.Execute(`SELECT min(v) AS mn FROM stream GROUP BY CountingWindow(3) HAVING mn > 5`)) ch1 := make(chan []map[string]any, 4) @@ -28,11 +28,11 @@ func TestWindowHavingSustainedDetection(t *testing.T) { require.Len(t, rows, 1) assert.Equal(t, 9.0, rows[0]["mn"]) case <-time.After(5 * time.Second): - t.Fatal("timeout: 全 >5 的窗口应通过 HAVING") + t.Fatal("timeout: The windows of the >5 should pass through HAVING") } s1.Stop() - // 窗口含 dip → 被 HAVING 拦住,不应输出含数值的结果。 + // If the window contains dip → is blocked by HAVING and should not output a result containing the value. s2 := streamsql.New() require.NoError(t, s2.Execute(`SELECT min(v) AS mn FROM stream GROUP BY CountingWindow(3) HAVING mn > 5`)) ch2 := make(chan []map[string]any, 4) @@ -44,16 +44,16 @@ func TestWindowHavingSustainedDetection(t *testing.T) { case rows := <-ch2: for _, r := range rows { if mn, ok := r["mn"]; ok && mn != nil { - t.Fatalf("含 dip 的窗口应被 HAVING 拦住,却收到 mn=%v", mn) + t.Fatalf("A window containing dip should be blocked by HAVING, but receives mn=%v", mn) } } case <-time.After(500 * time.Millisecond): - // 期望:无输出(HAVING 拦住了 dip 窗口)。 + // Expectation: No output (HAVING blocks the dip window). } s2.Stop() } -// TestWindowOverRejected:GROUP BY 窗口上的 OVER(...) 一律拒绝,引导用 HAVING。 +// TestWindowOverRejected: OVER on the GROUP BY window is always rejected, and HAVING is used for guidance. func TestWindowOverRejected(t *testing.T) { t.Parallel() for _, sql := range []string{ @@ -68,9 +68,9 @@ func TestWindowOverRejected(t *testing.T) { } } -// TestPerRowWindowFunctionsRejectedAtExecute:row_number()/lead() 已从注册表移除, -// 引用须在 Execute 期(解析报未知函数)失败,而非静默返回 nil 或崩数据路径。 -// 回归"注册但未接线"的半成品。 +// TestPerRowWindowFunctionsRejectedAtExecute:row_number()/lead() has been removed from the registry, +// References must fail during the Execute period (when parsing unknown functions), rather than silently returning nil or crashing data paths. +// Returning to the "registered but unwired" half-finished product. func TestPerRowWindowFunctionsRejectedAtExecute(t *testing.T) { t.Parallel() cases := []struct { diff --git a/test/e2e/window_session_test.go b/test/e2e/window_session_test.go index f948b62..a147ec9 100644 --- a/test/e2e/window_session_test.go +++ b/test/e2e/window_session_test.go @@ -10,8 +10,8 @@ import ( "github.com/stretchr/testify/require" ) -// TestSQLSessionWindow_ProcessingTime 测试处理时间的会话窗口 -// 验证不使用 WITH 子句时,会话窗口基于处理时间(系统时钟)工作 +// TestSQLSessionWindow_ProcessingTime Session window for testing processing time +// When verification does not use the WITH clause, the session window operates based on processing time (system clock). func TestSQLSessionWindow_ProcessingTime(t *testing.T) { t.Parallel() ssql := streamsql.New() @@ -30,23 +30,23 @@ func TestSQLSessionWindow_ProcessingTime(t *testing.T) { ssql.AddSink(func(results []map[string]any) { defer func() { if r := recover(); r != nil { - // channel 已关闭,忽略错误 + // channel is closed, ignoring errors } }() ch <- results }) - // 使用处理时间:发送数据,不包含时间戳字段 - // 会话窗口基于数据到达的处理时间(系统时钟)来划分会话 + // Use processing time: Send data without timestamp fields + // The session window divides sessions based on the processing time (system clock) of data arrival for i := 0; i < 5; i++ { ssql.Emit(map[string]any{ "deviceId": "sensor001", "temperature": float64(i), }) - time.Sleep(50 * time.Millisecond) // 数据间隔小于会话超时时间(300ms),属于同一会话 + time.Sleep(50 * time.Millisecond) // If the data interval is less than the session timeout (300ms), it belongs to the same session } - // 等待会话超时(处理时间会话窗口基于系统时钟触发) + // Waiting for session timeout (processing time session window triggered by system clock) time.Sleep(600 * time.Millisecond) select { @@ -55,9 +55,9 @@ func TestSQLSessionWindow_ProcessingTime(t *testing.T) { row := res[0] assert.Equal(t, "sensor001", row["deviceId"]) assert.Equal(t, float64(5), row["cnt"]) - t.Logf("处理时间会话窗口成功触发,数据量: %.0f", row["cnt"]) + t.Logf("Processing time session window successfully triggered, data volume: %.0f", row["cnt"]) case <-time.After(2 * time.Second): - t.Fatal("处理时间会话窗口应该触发") + t.Fatal("The processing time session window should be triggered") } } @@ -79,7 +79,7 @@ func TestSQLSessionWindow_GroupedSession_MixedDevices(t *testing.T) { ssql.AddSink(func(results []map[string]any) { defer func() { if r := recover(); r != nil { - // channel 已关闭,忽略错误 + // channel is closed, ignoring errors } }() ch <- results @@ -137,7 +137,7 @@ func TestSQLSessionWindow_MultiKeyGroupedSession(t *testing.T) { ssql.AddSink(func(results []map[string]any) { defer func() { if r := recover(); r != nil { - // channel 已关闭,忽略错误 + // channel is closed, ignoring errors } }() ch <- results @@ -210,7 +210,7 @@ func TestSQLSessionWindow_MultiKeyGroupedSession(t *testing.T) { assert.Equal(t, 33.0, got["B|R2"].max) } -// TestSQLSessionWindow_EventTimeWithWithClause 测试使用 WITH 子句指定事件时间的会话窗口 +// TestSQLSessionWindow_EventTimeWithWithClause Test session windows that use the WITH clause to specify event time func TestSQLSessionWindow_EventTimeWithWithClause(t *testing.T) { t.Parallel() ssql := streamsql.New() @@ -230,33 +230,33 @@ func TestSQLSessionWindow_EventTimeWithWithClause(t *testing.T) { ssql.AddSink(func(results []map[string]any) { defer func() { if r := recover(); r != nil { - // channel 已关闭,忽略错误 + // channel is closed, ignoring errors } }() ch <- results }) - // 使用事件时间:发送带有事件时间戳的数据 - baseTime := time.Now().UnixMilli() - 5000 // 5秒前作为基准时间 + // Using event time: Send data with event timestamps + baseTime := time.Now().UnixMilli() - 5000 // 5 seconds ago as the reference time for i := 0; i < 5; i++ { - eventTime := baseTime + int64(i*50) // 每50ms一条数据 + eventTime := baseTime + int64(i*50) // One data sheet every 50ms ssql.Emit(map[string]any{ "deviceId": "sensor001", - "eventTime": eventTime, // 事件时间字段 + "eventTime": eventTime, // Event time field }) - time.Sleep(20 * time.Millisecond) // 处理时间间隔较小 + time.Sleep(20 * time.Millisecond) // Processing time intervals are smaller } - // 发送一个事件时间超过会话结束时间的数据,推进watermark - // 会话结束时间 = baseTime + 200 + 300 = baseTime + 500 - // 需要发送事件时间 > baseTime + 500 + maxOutOfOrderness(200) = baseTime + 700 的数据 - // 使用不同的设备ID,避免影响当前会话的计数 + // Send data on an event that lasts longer than the session ends, and push the watermark + // Session end time = baseTime + 200 + 300 = baseTime + 500 + // The event time > baseTime + 500 + maxOutOfOrderness(200) = baseTime + 700 data is required + // Use different device IDs to avoid affecting the count of the current session ssql.Emit(map[string]any{ - "deviceId": "sensor002", // 使用不同的设备ID,不影响sensor001的会话 - "eventTime": baseTime + 2000, // 推进watermark + "deviceId": "sensor002", // Using different device IDs does not affect sensor001's session + "eventTime": baseTime + 2000, // Push the watermark }) - // 等待会话超时(事件时间会话窗口基于watermark触发) + // Waiting for session timeout (event time session window triggered by watermark) time.Sleep(1 * time.Second) select { @@ -265,13 +265,13 @@ func TestSQLSessionWindow_EventTimeWithWithClause(t *testing.T) { row := res[0] assert.Equal(t, "sensor001", row["deviceId"]) assert.Equal(t, float64(5), row["cnt"]) - t.Logf("事件时间会话窗口成功触发,数据量: %.0f", row["cnt"]) + t.Logf("Event time session window successfully triggered, data amount: %.0f", row["cnt"]) case <-time.After(2 * time.Second): - t.Fatal("事件时间会话窗口应该触发") + t.Fatal("The event time session window should be triggered") } } -// TestSQLSessionWindow_ProcessingTimeWithoutWithClause 测试不使用 WITH 子句时默认使用处理时间 +// TestSQLSessionWindow_ProcessingTimeWithoutWithClause When testing without the WITH clause, processing time is used by default func TestSQLSessionWindow_ProcessingTimeWithoutWithClause(t *testing.T) { t.Parallel() ssql := streamsql.New() @@ -290,13 +290,13 @@ func TestSQLSessionWindow_ProcessingTimeWithoutWithClause(t *testing.T) { ssql.AddSink(func(results []map[string]any) { defer func() { if r := recover(); r != nil { - // channel 已关闭,忽略错误 + // channel is closed, ignoring errors } }() ch <- results }) - // 不使用事件时间字段,应该使用处理时间 + // Do not use the event time field; the processing time should be used for i := 0; i < 5; i++ { ssql.Emit(map[string]any{ "deviceId": "sensor001", @@ -304,7 +304,7 @@ func TestSQLSessionWindow_ProcessingTimeWithoutWithClause(t *testing.T) { time.Sleep(50 * time.Millisecond) } - // 等待会话超时(处理时间会话窗口基于系统时钟) + // Waiting for session timeout (processing time session window based on system clock) time.Sleep(600 * time.Millisecond) select { @@ -313,13 +313,13 @@ func TestSQLSessionWindow_ProcessingTimeWithoutWithClause(t *testing.T) { row := res[0] assert.Equal(t, "sensor001", row["deviceId"]) assert.Equal(t, float64(5), row["cnt"]) - t.Logf("处理时间会话窗口成功触发,数据量: %.0f", row["cnt"]) + t.Logf("Processing time session window successfully triggered, data volume: %.0f", row["cnt"]) case <-time.After(2 * time.Second): - t.Fatal("处理时间会话窗口应该触发") + t.Fatal("The processing time session window should be triggered") } } -// TestSQLSessionWindow_EventTimeWindowAlignment 测试事件时间会话窗口 +// TestSQLSessionWindow_EventTimeWindowAlignment Test event time session window func TestSQLSessionWindow_EventTimeWindowAlignment(t *testing.T) { t.Parallel() ssql := streamsql.New() @@ -349,15 +349,15 @@ func TestSQLSessionWindow_EventTimeWindowAlignment(t *testing.T) { } }) - // 使用事件时间:发送数据,验证会话窗口基于事件时间触发 + // Using event time: Send data and validate session windows triggered based on event time baseTime := time.Now().UnixMilli() - 5000 sessionTimeoutMs := int64(500) - // 第一阶段:发送连续的数据(事件时间间隔小于sessionTimeout) - // 这些数据应该属于同一个会话 - t.Log("第一阶段:发送连续数据(同一会话)") + // Phase One: Send continuous data (event interval less than sessionTimeout) + // These data should belong to the same session + t.Log("Phase One: Sending Continuous Data (Same Session)") for i := 0; i < 5; i++ { - eventTime := baseTime + int64(i*100) // 每100ms一条,小于500ms超时 + eventTime := baseTime + int64(i*100) // One piece every 100ms, timeout less than 500ms ssql.Emit(map[string]any{ "deviceId": "sensor001", "eventTime": eventTime, @@ -366,13 +366,13 @@ func TestSQLSessionWindow_EventTimeWindowAlignment(t *testing.T) { time.Sleep(50 * time.Millisecond) } - // 等待一段时间,让watermark推进 - // 第一个会话的结束时间 = baseTime + 400 + 500 = baseTime + 900 - // 需要发送事件时间 > baseTime + 900 + maxOutOfOrderness(200) = baseTime + 1100 的数据 - // 才能让 watermark >= baseTime + 900,触发第一个会话 + // Wait a while and let Watermark advance + // End time of the first session = baseTime + 400 + 500 = baseTime + 900 + // Send event time > baseTime + 900 + maxOutOfOrderness(200) = baseTime + 1100 data + // Only then can watermark > = baseTime + 900 trigger the first session time.Sleep(500 * time.Millisecond) - // 发送数据推进watermark + // Send data to advance the watermark ssql.Emit(map[string]any{ "deviceId": "sensor001", "eventTime": baseTime + int64(1500), @@ -380,17 +380,17 @@ func TestSQLSessionWindow_EventTimeWindowAlignment(t *testing.T) { }) time.Sleep(500 * time.Millisecond) - // 第二阶段:发送间隔较大的数据(事件时间间隔大于sessionTimeout) - // 这应该触发新会话 - t.Log("第二阶段:发送间隔较大的数据(新会话)") - eventTime := baseTime + int64(2000) // 间隔2秒,大于500ms超时 + // Stage Two: Send data with longer intervals (event intervals greater than sessionTimeout) + // This should trigger a new session + t.Log("Phase Two: Sending Data with Longer Intervals (New Sessions)") + eventTime := baseTime + int64(2000) // 2-second interval, with a timeout greater than 500ms ssql.Emit(map[string]any{ "deviceId": "sensor001", "eventTime": eventTime, "temperature": 100.0, }) - // 继续发送连续数据(第二个会话) + // Continue sending continuous data (second session) for i := 0; i < 3; i++ { eventTime := baseTime + int64(2000+i*100) ssql.Emit(map[string]any{ @@ -401,17 +401,17 @@ func TestSQLSessionWindow_EventTimeWindowAlignment(t *testing.T) { time.Sleep(50 * time.Millisecond) } - // 推进watermark,触发会话 - // 会话结束时间 = baseTime + 400 + 500 = baseTime + 900 - // 需要发送事件时间 > baseTime + 900 + maxOutOfOrderness(200) = baseTime + 1100 的数据 - // 才能让 watermark >= baseTime + 900 + // Push the watermark to trigger a session + // Session end time = baseTime + 400 + 500 = baseTime + 900 + // Send event time > baseTime + 900 + maxOutOfOrderness(200) = baseTime + 1100 data + // Only then can watermark > = baseTime + 900 ssql.Emit(map[string]any{ "deviceId": "sensor001", "eventTime": baseTime + int64(5000), "temperature": 200.0, }) - // 继续发送更多数据,确保watermark推进 + // Keep sending more data to ensure Watermark advances for i := 0; i < 5; i++ { ssql.Emit(map[string]any{ "deviceId": "sensor001", @@ -421,14 +421,14 @@ func TestSQLSessionWindow_EventTimeWindowAlignment(t *testing.T) { time.Sleep(100 * time.Millisecond) } - // 等待会话触发(watermark更新间隔200ms,需要等待足够时间) + // Wait for session trigger (watermark update interval 200ms, requires sufficient time) time.Sleep(3 * time.Second) timeout := time.After(3 * time.Second) for { select { case <-ch: - // 继续收集结果 + // Continue collecting results case <-timeout: goto END } @@ -442,13 +442,13 @@ END: windowResultsMu.Unlock() if windowResultsLen == 0 { - t.Log("⚠ 会话窗口未触发,可能watermark未推进到足够位置") - t.Log("提示:会话窗口需要在 watermark >= session_end 时触发") - t.Log("会话结束时间 = 最后一个数据时间 + 超时时间") + t.Log("⚠ If the session window is not triggered, it may watermark not advanced to a sufficient position") + t.Log("Note: The session window needs to be triggered when watermark > = session_end") + t.Log("Session end time = last data time + timeout timeout") } require.Greater(t, windowResultsLen, 0, "应该至少触发一个会话窗口") - // 验证会话窗口 + // Validate the session window for i, window := range windowResultsCopy { if len(window) > 0 { row := window[0] @@ -456,10 +456,10 @@ END: end := row["end"].(int64) cnt := row["cnt"].(float64) - // 验证会话窗口有数据 + // Verify that the session window has data assert.Greater(t, cnt, 0.0, "会话窗口 %d 应该包含数据", i+1) - // 验证会话窗口的时间范围合理 + // Verify that the time range of the session window is reasonable assert.Greater(t, end, start, "会话窗口 %d 的结束时间应该大于开始时间", i+1) startMs := start / int64(time.Millisecond) @@ -469,14 +469,14 @@ END: assert.GreaterOrEqual(t, sessionDurationMs, sessionTimeoutMs, "会话窗口 %d 的持续时间应该至少等于会话超时时间", i+1) - t.Logf("会话窗口 %d: [%d, %d), cnt=%.0f, duration=%dms", i+1, startMs, endMs, cnt, sessionDurationMs) + t.Logf("Session window %d: [%d, %d), cnt=%.0f, duration=%dms", i+1, startMs, endMs, cnt, sessionDurationMs) } } - t.Logf("总共触发了 %d 个会话窗口", windowResultsLen) + t.Logf("A total of %d session windows were triggered", windowResultsLen) } -// TestSQLSessionWindow_WatermarkTriggerTiming 测试会话窗口Watermark触发时机 +// TestSQLSessionWindow_WatermarkTriggerTiming Test the timing of triggering the session window Watermark func TestSQLSessionWindow_WatermarkTriggerTiming(t *testing.T) { t.Parallel() ssql := streamsql.New() @@ -510,12 +510,12 @@ func TestSQLSessionWindow_WatermarkTriggerTiming(t *testing.T) { maxOutOfOrdernessMs := int64(200) sessionTimeoutMs := int64(500) - // 发送数据,创建会话 - // 第一个数据:baseTime - // 后续数据:baseTime + 100, baseTime + 200, baseTime + 300, baseTime + 400 - // 会话结束时间应该是 baseTime + 400 + 500 = baseTime + 900 - // 当watermark >= baseTime + 900时,会话应该触发 - t.Log("发送数据创建会话") + // Send data and create sessions + // The first data point: baseTime + // Subsequent data: baseTime +100, baseTime +200, baseTime +300, baseTime +400 + // The session end time should be baseTime + 400 + 500 = baseTime + 900 + // When watermark > = baseTime + 900, the session should be triggered + t.Log("Send data to create a session") for i := 0; i < 5; i++ { eventTime := baseTime + int64(i*100) ssql.Emit(map[string]any{ @@ -526,31 +526,31 @@ func TestSQLSessionWindow_WatermarkTriggerTiming(t *testing.T) { time.Sleep(50 * time.Millisecond) } - // 计算会话结束时间 - sessionEndTime := baseTime + int64(400) + sessionTimeoutMs // 最后一个数据时间 + 超时时间 + // Calculate the end time of the session + sessionEndTime := baseTime + int64(400) + sessionTimeoutMs // Last data time + timeout - // 发送一个事件时间刚好等于sessionEndTime的数据 + // Send data where the event time is exactly equal to sessionEndTime // watermark = maxEventTime - maxOutOfOrderness = sessionEndTime - 200 - // 此时 watermark < sessionEndTime,会话不应该触发 + // At this time, the watermark < sessionEndTime, and the session should not be triggered ssql.Emit(map[string]any{ "deviceId": "sensor001", "eventTime": sessionEndTime, "temperature": 100.0, }) - // 等待watermark更新 + // Waiting for the watermark update time.Sleep(500 * time.Millisecond) - // 发送一个事件时间超过sessionEndTime的数据,推进watermark + // Send data with an event time exceeding sessionEndTime, advancing the watermark // watermark = maxEventTime - maxOutOfOrderness = (sessionEndTime + 500) - 200 = sessionEndTime + 300 - // 此时 watermark >= sessionEndTime,会话应该触发 + // At this point, watermark > = sessionEndTime, and the session should be triggered ssql.Emit(map[string]any{ "deviceId": "sensor001", "eventTime": sessionEndTime + 1000, "temperature": 200.0, }) - // 继续发送更多数据,确保watermark推进 + // Keep sending more data to ensure Watermark advances for i := 0; i < 3; i++ { ssql.Emit(map[string]any{ "deviceId": "sensor001", @@ -560,14 +560,14 @@ func TestSQLSessionWindow_WatermarkTriggerTiming(t *testing.T) { time.Sleep(100 * time.Millisecond) } - // 等待会话触发(watermark更新间隔200ms,需要等待足够时间) + // Wait for session trigger (watermark update interval 200ms, requires sufficient time) time.Sleep(3 * time.Second) timeout := time.After(3 * time.Second) for { select { case <-ch: - // 继续收集结果 + // Continue collecting results case <-timeout: goto END } @@ -581,13 +581,13 @@ END: windowResultsMu.Unlock() if windowResultsLen == 0 { - t.Log("⚠ 会话窗口未触发,可能watermark未推进到足够位置") - t.Log("提示:会话窗口需要在 watermark >= session_end 时触发") - t.Log("会话结束时间 = 最后一个数据时间 + 超时时间") + t.Log("⚠ If the session window is not triggered, it may watermark not advanced to a sufficient position") + t.Log("Note: The session window needs to be triggered when watermark > = session_end") + t.Log("Session end time = last data time + timeout timeout") } require.Greater(t, windowResultsLen, 0, "应该至少触发一个会话窗口") - // 验证会话窗口的触发时机 + // Verify the timing of the session window trigger if windowResultsLen > 0 { firstWindow := windowResultsCopy[0] if len(firstWindow) > 0 { @@ -596,7 +596,7 @@ END: end := row["end"].(int64) cnt := row["cnt"].(float64) - // 验证会话窗口包含数据 + // The validation session window contains data assert.Greater(t, cnt, 0.0, "会话窗口应该包含数据") startMs := start / int64(time.Millisecond) @@ -606,14 +606,14 @@ END: assert.GreaterOrEqual(t, sessionDurationMs, sessionTimeoutMs, "会话窗口的持续时间应该至少等于会话超时时间") - t.Logf("✓ 会话窗口在watermark >= session_end时正确触发") - t.Logf("会话窗口: [%d, %d), cnt=%.0f, 触发时maxEventTime >= %d", + t.Logf("✓ The session window is correctly triggered when watermark > = session_end") + t.Logf("Session window: [%d, %d), cnt=%.0f, triggered maxEventTime >=%d", start, end, cnt, end+maxOutOfOrdernessMs) } } } -// TestSQLSessionWindow_IdleSourceMechanism 测试会话窗口的Idle Source机制 +// TestSQLSessionWindow_IdleSourceMechanism Test the Idle Source mechanism of the session window func TestSQLSessionWindow_IdleSourceMechanism(t *testing.T) { t.Parallel() ssql := streamsql.New() @@ -645,8 +645,8 @@ func TestSQLSessionWindow_IdleSourceMechanism(t *testing.T) { baseTime := time.Now().UnixMilli() - 5000 - // 发送数据,创建会话 - t.Log("发送数据,创建会话") + // Send data and create sessions + t.Log("Send data and create sessions") for i := 0; i < 5; i++ { eventTime := baseTime + int64(i*100) ssql.Emit(map[string]any{ @@ -657,16 +657,16 @@ func TestSQLSessionWindow_IdleSourceMechanism(t *testing.T) { time.Sleep(50 * time.Millisecond) } - // 停止发送数据,等待Idle Source机制触发 - t.Log("停止发送数据,等待Idle Source机制触发(IdleTimeout=2s)") + // Stop sending data and wait for the Idle Source mechanism to trigger + t.Log("Stop sending data and wait for the Idle Source mechanism to trigger (IdleTimeout=2s)") time.Sleep(3 * time.Second) - // 收集窗口结果 + // Collect window results timeout := time.After(3 * time.Second) for { select { case <-ch: - // 继续收集结果 + // Continue collecting results case <-timeout: goto END } @@ -682,14 +682,14 @@ END: require.Greater(t, windowResultsLen, 0, "应该至少触发一个会话窗口(即使数据源空闲)") if windowResultsLen > 0 { - t.Logf("✓ 会话窗口Idle Source机制正常工作,触发了 %d 个会话", windowResultsLen) + t.Logf("✓ The Idle Source session window mechanism worked properly, triggering %d sessions", windowResultsLen) for i, window := range windowResultsCopy { if len(window) > 0 { row := window[0] start := row["start"].(int64) end := row["end"].(int64) cnt := row["cnt"].(float64) - t.Logf("会话 %d: [%d, %d), cnt=%.0f", i+1, start, end, cnt) + t.Logf("Conversation %d: [%d, %d), cnt=%.0f", i+1, start, end, cnt) } } } diff --git a/test/e2e/window_sliding_test.go b/test/e2e/window_sliding_test.go index 5e0a42c..0da01aa 100644 --- a/test/e2e/window_sliding_test.go +++ b/test/e2e/window_sliding_test.go @@ -11,8 +11,8 @@ import ( "github.com/stretchr/testify/require" ) -// TestSQLSlidingWindow_ProcessingTime 测试处理时间的滑动窗口 -// 验证不使用 WITH 子句时,滑动窗口基于处理时间(系统时钟)工作 +// TestSQLSlidingWindow_ProcessingTime Sliding window for test processing time +// When the WITH clause is not used, the sliding window operates based on processing time (system clock). func TestSQLSlidingWindow_ProcessingTime(t *testing.T) { t.Parallel() ssql := streamsql.New() @@ -39,11 +39,11 @@ func TestSQLSlidingWindow_ProcessingTime(t *testing.T) { } }) - // 每200ms发送一条数据,持续发送3秒,确保有足够的数据 - // 数据会在处理时间到达时被添加到窗口 + // Send a piece of data every 200ms, continuously for 3 seconds, ensuring sufficient data is available + // Data is added to the window when processing time is up done := make(chan bool, 1) // buffered: send never blocks (the signal is not awaited) go func() { - for i := 0; i < 15; i++ { // 发送15条数据,约3秒 + for i := 0; i < 15; i++ { // Send 15 data entries, about 3 seconds ssql.Emit(map[string]any{ "deviceId": "sensor001", "temperature": i, @@ -53,7 +53,7 @@ func TestSQLSlidingWindow_ProcessingTime(t *testing.T) { done <- true }() - // 等待窗口触发(第一个窗口应该在2秒后触发) + // Wait for the window to trigger (the first window should open after 2 seconds) time.Sleep(3 * time.Second) results := make([][]map[string]any, 0) @@ -83,29 +83,29 @@ END: require.Len(t, firstWindow, 1, "第一个窗口应该只有一行结果") cnt := firstWindow[0]["cnt"].(float64) - // 验证第一个窗口包含数据 + // Verify that the first window contains data assert.Greater(t, cnt, 0.0, "第一个窗口应该包含数据") - // 使用处理时间时,窗口基于数据到达的处理时间 - // 窗口大小2秒,每200ms发送一条数据 - // 第一个窗口应该在窗口大小时间(2秒)后触发 - // 在2秒内,应该会发送10条数据(每200ms 1条) + // When using processing time, the window is based on the processing time of data arrival + // The window size is 2 seconds, and a data piece is sent every 200ms + // The first window should be triggered after the window size time (2 seconds). + // Within 2 seconds, 10 data messages should be sent (one every 200ms). - // 验证第一个窗口的数据量应该在合理范围内 - // 使用处理时间时,窗口包含的是在窗口大小时间内到达的所有数据 - // 窗口大小2秒,每200ms 1条,应该包含约10条数据 + // The amount of data in the first window should be within a reasonable range + // When using processing time, the window contains all data arriving within the window size timeframe + // The window size is 2 seconds, with one line every 200ms, and should contain about 10 data lines assert.GreaterOrEqual(t, cnt, 5.0, "第一个窗口应该包含足够的数据(窗口大小2秒,每200ms 1条),实际: %.0f", cnt) assert.LessOrEqual(t, cnt, 15.0, "第一个窗口不应该超过15条数据,实际: %.0f", cnt) - t.Logf("第一个窗口数据量: %.0f(使用处理时间,窗口大小2秒,每200ms 1条数据)", cnt) + t.Logf("First window data size: %.0f (processing time, window size 2 seconds, 1 data entry per 200ms)", cnt) } - // 验证有多个窗口被触发(滑动窗口应该每2秒触发一次) + // Verify that multiple windows are triggered (sliding windows should be triggered every 2 seconds) if windowResultsLen > 1 { - t.Logf("总共触发了 %d 个窗口", windowResultsLen) - // 验证后续窗口也包含数据 + t.Logf("A total of %d windows were triggered", windowResultsLen) + // Verify that subsequent windows also contain data for i := 1; i < windowResultsLen && i < 5; i++ { if len(windowResultsCopy[i]) > 0 { cnt := windowResultsCopy[i][0]["cnt"].(float64) @@ -137,13 +137,13 @@ func TestSQLSlidingWindow_WithAggregations(t *testing.T) { ssql.AddSink(func(results []map[string]any) { defer func() { if r := recover(); r != nil { - // channel 已关闭,忽略错误 + // channel is closed, ignoring errors } }() ch <- results }) - // 使用处理时间,每200ms发送一条数据 + // Using processing time, one data piece is sent every 200ms for i := 0; i < 15; i++ { temperature := float64(i) ssql.Emit(map[string]any{ @@ -228,13 +228,13 @@ func TestSQLSlidingWindow_MultipleWindowsAlignment(t *testing.T) { ssql.AddSink(func(results []map[string]any) { defer func() { if r := recover(); r != nil { - // channel 已关闭,忽略错误 + // channel is closed, ignoring errors } }() ch <- results }) - // 使用处理时间,每200ms发送一条数据 + // Using processing time, one data piece is sent every 200ms for i := 0; i < 15; i++ { ssql.Emit(map[string]any{ "deviceId": "sensor001", @@ -293,8 +293,8 @@ END: assert.LessOrEqual(t, cnt, 15.0, "窗口 %d 计数不应该超过15", i+1) } - // D1 后处理时间窗口对齐到 epoch,窗口相位不再由首条数据决定,且并行负载下 - // 结果接收顺序不确定——跨所有窗口聚合检查,不断言首个/末个接收到的窗口。 + // The D1 post-processing time window is aligned to epoch, so the window phase is no longer determined by the first data and is under parallel load + // The order of receiving results is uncertain—aggregate checks across all windows, constantly stating the first/last received window. globalMin, globalMax := 14.0, 0.0 for _, res := range windowResultsCopy { if len(res) == 0 { @@ -333,13 +333,13 @@ func TestSQLSlidingWindow_MultiKeyGrouped(t *testing.T) { ssql.AddSink(func(results []map[string]any) { defer func() { if r := recover(); r != nil { - // channel 已关闭,忽略错误 + // channel is closed, ignoring errors } }() ch <- results }) - // 使用处理时间,每200ms发送一组数据 + // Using processing time, a set of data is sent every 200ms for i := 0; i < 8; i++ { ssql.Emit(map[string]any{ "deviceId": "A", @@ -420,8 +420,8 @@ END: } } -// TestSQLSlidingWindow_FirstWindowTiming 测试第一个窗口的触发时机 -// 验证第一个窗口应该在窗口大小时间后触发,而不是滑动步长时间后触发 +// TestSQLSlidingWindow_FirstWindowTiming Test the trigger timing of the first window +// Verify that the first window should be triggered after the window size time, not after a long swipe step func TestSQLSlidingWindow_FirstWindowTiming(t *testing.T) { t.Parallel() ssql := streamsql.New() @@ -444,7 +444,7 @@ func TestSQLSlidingWindow_FirstWindowTiming(t *testing.T) { ssql.AddSink(func(results []map[string]any) { defer func() { if r := recover(); r != nil { - // channel 已关闭,忽略错误 + // channel is closed, ignoring errors } }() if len(results) > 0 { @@ -455,31 +455,31 @@ func TestSQLSlidingWindow_FirstWindowTiming(t *testing.T) { } }) - // 记录第一个数据发送时间 + // Record the time the first data is sent firstDataTime := time.Now() baseTime := time.Now().UnixMilli() - // 使用事件时间,每200ms发送一条数据,共发送10条 + // Using event time, one data piece is sent every 200ms, for a total of 10 messages for i := 0; i < 10; i++ { ssql.Emit(map[string]any{ "deviceId": "sensor001", - "timestamp": baseTime + int64(i*200), // 添加timestamp字段 + "timestamp": baseTime + int64(i*200), // Add the timestamp field "temperature": float64(i), }) time.Sleep(200 * time.Millisecond) } - // 发送一个事件时间超过第一个窗口结束时间的数据,推进watermark - // 窗口大小2秒,第一个窗口应该在 [baseTime, baseTime+2000) 范围内 - // 发送一个事件时间为 baseTime+3000 的数据来推进watermark + // Send data from an event that lasts longer than the first window ends, and push the watermark + // The window size is 2 seconds, and the first window should be within the range of [baseTime, baseTime+2000). + // Send data with event time baseTime+3000 to advance the watermark ssql.Emit(map[string]any{ "deviceId": "sensor001", - "timestamp": baseTime + 3000, // 推进watermark + "timestamp": baseTime + 3000, // Push the watermark "temperature": 100.0, }) - // 等待第一个窗口触发(应该在窗口大小2秒后,而不是滑动步长500ms后) - // 发送完数据后,等待足够的时间让第一个窗口触发 + // Wait for the first window to trigger (it should be after 2 seconds of window size, not after sliding steps 500ms). + // After sending the data, wait enough time for the first window to trigger time.Sleep(3 * time.Second) timeout := time.After(2 * time.Second) @@ -496,20 +496,20 @@ func TestSQLSlidingWindow_FirstWindowTiming(t *testing.T) { windowTimingsMu.Unlock() elapsed := firstWindowTime.Sub(firstDataTime) - // 第一个窗口应该在窗口大小时间(2秒)后触发 - // 允许一些误差(±500ms),因为数据处理和调度可能有延迟 + // The first window should be triggered after the window size time (2 seconds). + // Some errors (±500ms) are allowed, as data processing and scheduling may have delays assert.GreaterOrEqual(t, elapsed, 1500*time.Millisecond, "第一个窗口应该在窗口大小时间(2秒)后触发,实际耗时: %v", elapsed) assert.LessOrEqual(t, elapsed, 5*time.Second, "第一个窗口不应该太晚触发,实际耗时: %v", elapsed) - // 验证第一个窗口不应该在滑动步长时间(500ms)后就触发 + // Verify that the first window should not be triggered after a long swipe step (500ms). assert.Greater(t, elapsed, 800*time.Millisecond, "第一个窗口不应该在滑动步长时间(500ms)后就触发,实际耗时: %v", elapsed) cnt := res[0]["cnt"].(float64) assert.Greater(t, cnt, 0.0, "第一个窗口应该包含数据") - t.Logf("第一个窗口触发时间: %v, 从第一个数据到触发耗时: %v, 窗口数据量: %.0f", + t.Logf("First window trigger time: %v, time from first data to trigger: %v, window data amount: %.0f", firstWindowTime, elapsed, cnt) } else { windowTimingsMu.Unlock() @@ -524,8 +524,8 @@ END: assert.True(t, firstWindowReceived, "应该至少收到第一个窗口") } -// TestSQLSlidingWindow_DataOverlap 测试滑动窗口的数据重叠正确性 -// 验证数据在多个窗口中正确保留,不会过早清理 +// TestSQLSlidingWindow_DataOverlap Test the accuracy of data overlap in the sliding window +// Validation data is correctly preserved across multiple windows and is not cleaned prematurely func TestSQLSlidingWindow_DataOverlap(t *testing.T) { t.Parallel() ssql := streamsql.New() @@ -549,7 +549,7 @@ func TestSQLSlidingWindow_DataOverlap(t *testing.T) { ssql.AddSink(func(results []map[string]any) { defer func() { if r := recover(); r != nil { - // channel 已关闭,忽略错误 + // channel is closed, ignoring errors } }() if len(results) > 0 { @@ -560,9 +560,9 @@ func TestSQLSlidingWindow_DataOverlap(t *testing.T) { } }) - // 使用处理时间,每200ms发送一条数据,共发送15条 - // 窗口大小2秒,滑动步长500ms - // 使用处理时间时,窗口基于数据到达的处理时间 + // Using processing time, one data piece is sent every 200ms, for a total of 15 messages + // Window size is 2 seconds, sliding step length is 500ms + // When using processing time, the window is based on the processing time of data arrival for i := 0; i < 15; i++ { ssql.Emit(map[string]any{ "deviceId": "sensor001", @@ -571,17 +571,17 @@ func TestSQLSlidingWindow_DataOverlap(t *testing.T) { time.Sleep(200 * time.Millisecond) } - // 等待足够的时间让多个窗口触发 - // 第一个窗口在2秒后触发,后续窗口每500ms触发一次 - // 等待足够的时间让至少3个窗口触发:2秒(第一个窗口)+ 500ms(第二个窗口)+ 500ms(第三个窗口)= 3秒 + // Wait enough time for multiple windows to trigger + // The first window triggers after 2 seconds, and subsequent windows trigger every 500ms + // Wait enough time for at least 3 windows to trigger: 2 seconds (first window) + 500ms (second window) + 500ms (third window) = 3 seconds time.Sleep(3 * time.Second) - // 收集所有已到达的结果,设置合理的超时时间 + // Collect all the results you have received and set a reasonable timeout timeout timeout := time.After(2 * time.Second) for { select { case <-ch: - // 继续收集结果 + // Continue collecting results case <-timeout: goto END } @@ -595,7 +595,7 @@ END: windowResultsMu.Unlock() require.GreaterOrEqual(t, windowResultsLen, 3, "应该至少触发3个窗口") - // 验证第一个窗口包含数据0-9 + // Verify that the first window contains data 0-9 if windowResultsLen > 0 { firstWindow := windowResultsCopy[0] require.Len(t, firstWindow, 1) @@ -604,24 +604,24 @@ END: firstMin := firstRow["min_temp"].(float64) firstMax := firstRow["max_temp"].(float64) - // 使用处理时间时,第一个窗口应该包含在窗口大小时间内到达的数据 - // 窗口大小2秒,每200ms 1条数据,应该包含约10条数据 - // 但由于窗口对齐和数据处理延迟,实际数量可能略有不同 + // When using processing time, the first window should contain data that arrives within the window size time + // The window size is 2 seconds, with one data entry every 200ms, and it should contain about 10 data entries + // However, due to window alignment and data processing delays, the actual number may vary slightly assert.GreaterOrEqual(t, firstCnt, 5.0, "第一个窗口应该包含足够的数据(窗口大小2秒,每200ms 1条),实际: %.0f", firstCnt) assert.LessOrEqual(t, firstCnt, 15.0, "第一个窗口不应该超过15条数据,实际: %.0f", firstCnt) - // 第一个窗口的最小值应该是0或接近0 + // The minimum value of the first window should be 0 or close to 0 assert.LessOrEqual(t, firstMin, 1.0, "第一个窗口的最小值应该接近0,实际: %.0f", firstMin) - // 第一个窗口的最大值应该大于0 + // The maximum value of the first window should be greater than 0 assert.GreaterOrEqual(t, firstMax, 0.0, "第一个窗口的最大值应该大于等于0,实际: %.0f", firstMax) - t.Logf("第一个窗口: cnt=%.0f, min=%.0f, max=%.0f", firstCnt, firstMin, firstMax) + t.Logf("First window: cnt=%.0f, min=%.0f, max=%.0f", firstCnt, firstMin, firstMax) } - // 验证第二个窗口与第一个窗口有重叠 + // Verify that the second window overlaps with the first if windowResultsLen > 1 { secondWindow := windowResultsCopy[1] require.Len(t, secondWindow, 1) @@ -630,29 +630,29 @@ END: secondMin := secondRow["min_temp"].(float64) secondMax := secondRow["max_temp"].(float64) - // 使用处理时间时,第二个窗口也应该包含足够的数据 - // 窗口大小2秒,每200ms 1条数据,应该包含约10条数据 + // When using processing time, the second window should also contain enough data + // The window size is 2 seconds, with one data entry every 200ms, and it should contain about 10 data entries assert.GreaterOrEqual(t, secondCnt, 5.0, "第二个窗口应该包含足够的数据(窗口大小2秒,每200ms 1条),实际: %.0f", secondCnt) - // 验证重叠:第二个窗口的最小值应该大于第一个窗口的最小值 - // 因为窗口滑动,第二个窗口应该从数据2开始 + // Verify overlap: The minimum value in the second window should be greater than the minimum value in the first window + // Because the window slides, the second window should start from Data 2 if windowResultsLen > 0 { firstMin := windowResultsCopy[0][0]["min_temp"].(float64) assert.GreaterOrEqual(t, secondMin, firstMin, "第二个窗口的最小值应该大于等于第一个窗口的最小值,说明窗口正确滑动") } - t.Logf("第二个窗口: cnt=%.0f, min=%.0f, max=%.0f", secondCnt, secondMin, secondMax) + t.Logf("Second window: cnt = %.0f, min = %.0f, max = %.0f", secondCnt, secondMin, secondMax) } - // 验证窗口数据不会过早丢失 - // 检查是否有窗口的数据量异常少(可能是数据被过早清理) + // Validation window data is not lost prematurely + // Check if the window has abnormally low data (possibly because the data was cleared too early). for i, res := range windowResultsCopy { if len(res) > 0 { cnt := res[0]["cnt"].(float64) - // 对于前几个窗口,数据量不应该异常少 - // 使用处理时间时,窗口大小2秒,每200ms 1条,应该包含约10条数据 + // For the first few windows, the data volume should not be abnormally small + // When using processing time, the window size is 2 seconds, with one entry every 200ms, and it should contain about 10 data entries if i < 3 { assert.GreaterOrEqual(t, cnt, 5.0, "窗口 %d 的数据量不应该异常少,可能是数据被过早清理,实际: %.0f", i+1, cnt) @@ -661,8 +661,8 @@ END: } } -// TestSQLSlidingWindow_DataRetention 测试滑动窗口的数据保留逻辑 -// 验证数据在后续窗口中正确保留,不会过早清理 +// TestSQLSlidingWindow_DataRetention Test the data retention logic of the sliding window +// Validation data is correctly preserved in subsequent windows and is not cleaned prematurely func TestSQLSlidingWindow_DataRetention(t *testing.T) { t.Parallel() ssql := streamsql.New() @@ -686,7 +686,7 @@ func TestSQLSlidingWindow_DataRetention(t *testing.T) { ssql.AddSink(func(results []map[string]any) { defer func() { if r := recover(); r != nil { - // channel 已关闭,忽略错误 + // channel is closed, ignoring errors } }() if len(results) > 0 { @@ -697,9 +697,9 @@ func TestSQLSlidingWindow_DataRetention(t *testing.T) { } }) - // 使用处理时间,每200ms发送一条数据,共发送12条 - // 窗口大小2秒,滑动步长500ms - // 使用处理时间时,窗口基于数据到达的处理时间 + // Using processing time, one data piece is sent every 200ms, totaling 12 messages + // Window size is 2 seconds, sliding step length is 500ms + // When using processing time, the window is based on the processing time of data arrival for i := 0; i < 12; i++ { ssql.Emit(map[string]any{ "deviceId": "sensor001", @@ -708,19 +708,19 @@ func TestSQLSlidingWindow_DataRetention(t *testing.T) { time.Sleep(200 * time.Millisecond) } - // 等待多个窗口触发 - // 第一个窗口在2秒后触发,后续窗口每500ms触发一次 - // 等待足够的时间让至少3个窗口触发:2秒(第一个窗口)+ 500ms(第二个窗口)+ 500ms(第三个窗口)= 3秒 + // Wait for multiple windows to trigger + // The first window triggers after 2 seconds, and subsequent windows trigger every 500ms + // Wait enough time for at least 3 windows to trigger: 2 seconds (first window) + 500ms (second window) + 500ms (third window) = 3 seconds time.Sleep(3 * time.Second) - // 收集所有已到达的结果,设置合理的超时时间 - // 由于已经等待了15秒,大部分窗口应该已经触发 - // 这里只需要等待一小段时间收集剩余的结果 + // Collect all the results you have received and set a reasonable timeout timeout + // Since you've waited 15 seconds, most of the windows should have already been triggered + // Here, you just need to wait a short time to collect the remaining results timeout := time.After(2 * time.Second) for { select { case <-ch: - // 继续收集结果 + // Continue collecting results case <-timeout: goto END } @@ -734,9 +734,9 @@ END: windowResultsMu.Unlock() require.GreaterOrEqual(t, windowResultsLen, 3, "应该至少触发3个窗口") - // 验证数据保留:检查最小值的变化趋势 - // 由于窗口滑动,后续窗口的最小值应该逐渐增大 - // 但如果数据保留逻辑正确,不应该突然跳跃 + // Verify data retention: Check the trend of changes in the minimum value + // Due to window sliding, the minimum value of subsequent windows should gradually increase + // But if the data retention logic is correct, it should not jump abruptly minTemps := make([]float64, 0) for _, res := range windowResultsCopy { if len(res) > 0 { @@ -745,24 +745,24 @@ END: } } - // 验证最小值是递增或保持稳定的(不应该突然跳跃) + // Verify that the minimum value is increasing or stable (no sudden jumps should be made). for i := 1; i < len(minTemps); i++ { prevMin := minTemps[i-1] currMin := minTemps[i] - // 最小值应该递增或保持不变(窗口滑动导致) - // 但差值不应该太大(说明数据没有被过早清理) + // The minimum value should increase or remain unchanged (due to window sliding). + // But the difference shouldn't be too large (indicating the data hasn't been cleared too early). assert.GreaterOrEqual(t, currMin, prevMin-1.0, "窗口 %d 的最小值不应该比前一个窗口小太多,可能是数据被过早清理", i+1) } - // 验证窗口数据量:前几个窗口的数据量应该足够 - // 使用处理时间时,如果数据保留逻辑正确,窗口数据量应该逐渐减少(因为旧数据逐渐过期) - // 但减少应该是平滑的,不应该突然大幅减少 + // Validation window data volume: The data volume in the first few windows should be sufficient + // When using processing time, if the data retention logic is correct, the window data volume should gradually decrease (because old data gradually expires) + // But the reduction should be smooth, not sudden and dramatic for i := 0; i < windowResultsLen && i < 5; i++ { if len(windowResultsCopy[i]) > 0 { cnt := windowResultsCopy[i][0]["cnt"].(float64) - // 前几个窗口应该包含足够的数据(使用处理时间) - // 窗口大小2秒,每200ms 1条数据,应该包含约10条数据 + // The first few windows should contain enough data (using processing time) + // The window size is 2 seconds, with one data entry every 200ms, and it should contain about 10 data entries if i < 3 { assert.GreaterOrEqual(t, cnt, 5.0, "窗口 %d 应该包含足够的数据(窗口大小2秒,每200ms 1条),实际: %.0f", i+1, cnt) @@ -771,7 +771,7 @@ END: } } -// TestSQLSlidingWindow_EventTimeWithWithClause 测试使用 WITH 子句指定事件时间 +// TestSQLSlidingWindow_EventTimeWithWithClause Test using the WITH clause to specify event time func TestSQLSlidingWindow_EventTimeWithWithClause(t *testing.T) { t.Parallel() ssql := streamsql.New() @@ -794,7 +794,7 @@ func TestSQLSlidingWindow_EventTimeWithWithClause(t *testing.T) { ssql.AddSink(func(results []map[string]any) { defer func() { if r := recover(); r != nil { - // channel 已关闭,忽略错误 + // channel is closed, ignoring errors } }() if len(results) > 0 { @@ -805,30 +805,30 @@ func TestSQLSlidingWindow_EventTimeWithWithClause(t *testing.T) { } }) - // 使用事件时间:发送带有事件时间戳的数据 - // 事件时间从当前时间开始,每200ms递增,确保 watermark 能够推进 - baseTime := time.Now().UnixMilli() // 使用当前时间作为基准 + // Using event time: Send data with event timestamps + // The event time starts from the current time and increases every 200ms, ensuring the watermark can advance + baseTime := time.Now().UnixMilli() // Use the current time as the baseline for i := 0; i < 15; i++ { - eventTime := baseTime + int64(i*200) // 每200ms一条数据 + eventTime := baseTime + int64(i*200) // One data piece every 200ms ssql.Emit(map[string]any{ "deviceId": "sensor001", - "eventTime": eventTime, // 事件时间字段(毫秒) + "eventTime": eventTime, // Event time field (milliseconds) "temperature": float64(i), }) - time.Sleep(50 * time.Millisecond) // 处理时间间隔较小,模拟乱序 + time.Sleep(50 * time.Millisecond) // Processing intervals are short, simulating disorder in order } - // 等待窗口触发(事件时间窗口基于 watermark 触发) - // 窗口大小2秒,滑动步长500ms - // 第一个窗口应该在 watermark >= window_end 时触发 - // 由于 watermark 更新间隔是 200ms,需要等待足够的时间让 watermark 推进 + // Wait for window trigger (event time window triggered based on watermark) + // Window size is 2 seconds, sliding step length is 500ms + // The first window should be triggered when watermark > = window_end + // Since the watermark update interval is 200ms, you need to wait enough time for the watermark to advance time.Sleep(3 * time.Second) timeout := time.After(2 * time.Second) for { select { case <-ch: - // 继续收集结果 + // Continue collecting results case <-timeout: goto END } @@ -841,22 +841,22 @@ END: copy(windowResultsCopy, windowResults) windowResultsMu.Unlock() - // 事件时间窗口应该能够触发 - // 由于使用事件时间,窗口触发基于 watermark + // The event time window should be able to be triggered + // Because event time is used, window triggers are based on watermarks require.Greater(t, windowResultsLen, 0, "事件时间窗口应该至少触发一个窗口") if windowResultsLen > 0 { - t.Logf("事件时间窗口触发了 %d 个窗口", windowResultsLen) + t.Logf("The event time window triggered %d windows", windowResultsLen) firstWindow := windowResultsCopy[0] if len(firstWindow) > 0 { cnt := firstWindow[0]["cnt"].(float64) assert.Greater(t, cnt, 0.0, "事件时间窗口应该包含数据") - t.Logf("第一个事件时间窗口数据量: %.0f", cnt) + t.Logf("Data volume for the first event time window: %.0f", cnt) } } } -// TestSQLSlidingWindow_LateDataHandling 测试延迟数据的处理 -// 验证即使数据延迟到达,只要在允许的延迟范围内,也能正确统计到对应窗口 +// TestSQLSlidingWindow_LateDataHandling Processing of test delay data +// Verification: Even if data arrives with delay, as long as it is within the allowable delay range, the corresponding window can still be correctly counted func TestSQLSlidingWindow_LateDataHandling(t *testing.T) { t.Parallel() ssql := streamsql.New() @@ -881,7 +881,7 @@ func TestSQLSlidingWindow_LateDataHandling(t *testing.T) { ssql.AddSink(func(results []map[string]any) { defer func() { if r := recover(); r != nil { - // channel 已关闭,忽略错误 + // channel is closed, ignoring errors } }() if len(results) > 0 { @@ -892,45 +892,45 @@ func TestSQLSlidingWindow_LateDataHandling(t *testing.T) { } }) - // 使用事件时间:模拟延迟数据场景 - // 场景:先发送正常顺序的数据,然后发送一些延迟的数据 - baseTime := time.Now().UnixMilli() - 5000 // 使用5秒前作为基准,确保有足够的时间窗口 + // Using event time: simulates latency data scenarios + // Scenario: First send data in normal order, then send some delayed data + baseTime := time.Now().UnixMilli() - 5000 // Use 5 seconds before as the baseline to ensure there is a sufficient time window - // 第一阶段:发送正常顺序的数据(事件时间:0ms, 200ms, 400ms, ..., 2000ms) - // 这些数据应该被统计到第一个窗口 [0ms, 2000ms) - t.Log("第一阶段:发送正常顺序的数据") + // Phase One: Send data in normal order (event duration: 0ms, 200ms, 400ms,..., 2000ms) + // These data should be counted in the first window [0ms, 2000ms]. + t.Log("Stage One: Send data in normal order") for i := 0; i < 10; i++ { - eventTime := baseTime + int64(i*200) // 每200ms一条数据 + eventTime := baseTime + int64(i*200) // One data piece every 200ms ssql.Emit(map[string]any{ "deviceId": "sensor001", "eventTime": eventTime, - "temperature": float64(i), // 温度值 0-9 + "temperature": float64(i), // Temperature range: 0-9 }) - time.Sleep(50 * time.Millisecond) // 处理时间间隔较小 + time.Sleep(50 * time.Millisecond) // Processing time intervals are smaller } - // 等待 watermark 推进,让第一个窗口触发 - // 窗口大小2秒,第一个窗口应该在 watermark >= baseTime + 2000ms 时触发 - t.Log("等待 watermark 推进,触发第一个窗口") + // Wait for the watermark to advance and let the first window trigger + // The window size is 2 seconds, and the first window should be triggered when watermark > = baseTime + 2000ms + t.Log("Wait for watermark to advance and trigger the first window") time.Sleep(3 * time.Second) - // 第二阶段:发送延迟的数据 - // 这些数据的事件时间比之前的数据早,但应该在允许的延迟范围内 - // 延迟数据的事件时间:100ms, 300ms, 500ms(这些时间在第一个窗口 [0ms, 2000ms) 内) - t.Log("第二阶段:发送延迟数据(事件时间在第一个窗口内)") + // Stage Two: Send delayed data + // The event timing of these data is earlier than previous data, but it should be within the allowable delay range + // Event time for delayed data: 100ms, 300ms, 500ms (these times occur within the first window [0ms, 2000ms))') + t.Log("Stage Two: Sending Delayed Data (Event Time in the First Window)") for i := 0; i < 3; i++ { - // 延迟数据:事件时间比正常数据早,但仍在窗口范围内 + // Data delay: The event time is earlier than normal data but still within the window range eventTime := baseTime + int64(100+i*200) // 100ms, 300ms, 500ms ssql.Emit(map[string]any{ "deviceId": "sensor001", "eventTime": eventTime, - "temperature": float64(10 + i), // 温度值 10-12,用于区分延迟数据 + "temperature": float64(10 + i), // Temperature values of 10-12 are used to distinguish delayed data }) time.Sleep(100 * time.Millisecond) } - // 继续发送更多正常数据,推进 watermark - t.Log("第三阶段:继续发送正常数据,推进 watermark") + // Continue sending more normal data to advance Watermark + t.Log("Phase Three: Continue sending normal data and advance the watermark") for i := 10; i < 15; i++ { eventTime := baseTime + int64(i*200) ssql.Emit(map[string]any{ @@ -941,15 +941,15 @@ func TestSQLSlidingWindow_LateDataHandling(t *testing.T) { time.Sleep(50 * time.Millisecond) } - // 等待窗口触发和延迟数据处理 + // Wait for window triggers and delay data processing time.Sleep(3 * time.Second) - // 收集所有窗口结果 + // Collect all window results timeout := time.After(2 * time.Second) for { select { case <-ch: - // 继续收集结果 + // Continue collecting results case <-timeout: goto END } @@ -964,8 +964,8 @@ END: require.Greater(t, windowResultsLen, 0, "应该至少触发一个窗口") - // 验证第一个窗口的数据 - // 第一个窗口应该包含正常数据(0-9)和可能的延迟数据 + // Verify the data from the first window + // The first window should contain normal data (0-9) and possible latency data if windowResultsLen > 0 { firstWindow := windowResultsCopy[0] if len(firstWindow) > 0 { @@ -973,29 +973,29 @@ END: minTemp := firstWindow[0]["min_temp"].(float64) maxTemp := firstWindow[0]["max_temp"].(float64) - t.Logf("第一个窗口: cnt=%.0f, min=%.0f, max=%.0f", cnt, minTemp, maxTemp) + t.Logf("First window: cnt=%.0f, min=%.0f, max=%.0f", cnt, minTemp, maxTemp) - // 第一个窗口应该包含正常数据 - // 由于窗口对齐和 watermark 机制,实际数据量可能略有不同 + // The first window should contain normal data + // Due to window alignment and watermark mechanisms, the actual data volume may vary slightly assert.GreaterOrEqual(t, cnt, 5.0, "第一个窗口应该包含足够的数据") assert.Equal(t, 0.0, minTemp, "第一个窗口的最小值应该是0(正常数据)") assert.GreaterOrEqual(t, maxTemp, 0.0, "第一个窗口的最大值应该大于等于0") } } - // 验证延迟数据是否被处理 - // 如果延迟数据被正确处理,应该能在后续窗口或更新中看到 - t.Logf("总共触发了 %d 个窗口", windowResultsLen) + // Verify whether delayed data is being processed + // If delay data is handled correctly, it should be visible in future windows or updates + t.Logf("A total of %d windows were triggered", windowResultsLen) } -// TestSQLSlidingWindow_MaxOutOfOrderness 测试最大延迟时间配置 -// 验证设置 MaxOutOfOrderness 后,延迟数据能否在允许的延迟范围内被正确处理 +// TestSQLSlidingWindow_MaxOutOfOrderness Test the maximum latency configuration +// After verifying whether the MaxOutOfOrderness setting is properly processed within the allowable latency range, func TestSQLSlidingWindow_MaxOutOfOrderness(t *testing.T) { t.Parallel() ssql := streamsql.New() defer ssql.Stop() - // 使用 SQL 配置 MaxOutOfOrderness + // Configure MaxOutOfOrderness using SQL sql := ` SELECT deviceId, COUNT(*) as cnt, @@ -1015,7 +1015,7 @@ func TestSQLSlidingWindow_MaxOutOfOrderness(t *testing.T) { ssql.AddSink(func(results []map[string]any) { defer func() { if r := recover(); r != nil { - // channel 已关闭,忽略错误 + // channel is closed, ignoring errors } }() if len(results) > 0 { @@ -1026,17 +1026,17 @@ func TestSQLSlidingWindow_MaxOutOfOrderness(t *testing.T) { } }) - // 模拟延迟数据场景 - // 场景:设置 MaxOutOfOrderness = 1秒,测试延迟数据能否在1秒内被正确处理 - // 滑动窗口步长500ms,需要对齐到500ms的倍数 + // Simulating latency data scenarios + // Scenario: Set MaxOutOfOrderness = 1 second to test whether latency data is correctly processed within 1 second + // The sliding window steps are 500ms, which needs to be aligned to multiples of 500ms slideSizeMs := int64(500) // 500ms - baseTimeRaw := time.Now().UnixMilli() - 10000 // 使用10秒前作为基准 - // 对齐baseTime到滑动步长的倍数,确保窗口对齐行为可预测 + baseTimeRaw := time.Now().UnixMilli() - 10000 // Use 10 seconds before as the baseline + // Align the multiples of baseTime to the sliding step to ensure window alignment behavior is predictable baseTime := (baseTimeRaw / slideSizeMs) * slideSizeMs - // 第一阶段:发送正常顺序的数据 - // 事件时间:0ms, 200ms, 400ms, ..., 2000ms(第一个窗口 [0ms, 2000ms)) - t.Log("第一阶段:发送正常顺序的数据(事件时间 0-2000ms)") + // Stage One: Send data in normal order + // Event time: 0ms, 200ms, 400ms,..., 2000ms (first window [0ms, 2000ms))) + t.Log("Phase One: Send data in normal order (event time 0-2000ms)") for i := 0; i < 10; i++ { eventTime := baseTime + int64(i*200) ssql.Emit(map[string]any{ @@ -1047,37 +1047,37 @@ func TestSQLSlidingWindow_MaxOutOfOrderness(t *testing.T) { time.Sleep(50 * time.Millisecond) } - // 等待 watermark 推进,触发第一个窗口 - t.Log("等待 watermark 推进,触发第一个窗口") + // Wait for the watermark to advance, triggering the first window + t.Log("Wait for watermark to advance and trigger the first window") time.Sleep(3 * time.Second) - // 第二阶段:发送延迟数据 - // 延迟数据的事件时间在第一个窗口内(如 500ms, 700ms, 900ms) - // 如果 MaxOutOfOrderness = 1秒,这些数据应该能被处理 - t.Log("第二阶段:发送延迟数据(事件时间在第一个窗口内,延迟 < 1秒)") - lateDataTimes := []int64{500, 700, 900} // 延迟数据的事件时间(相对于 baseTime) + // Stage Two: Send delayed data + // The event time of the delayed data occurs within the first window (e.g., 500ms, 700ms, 900ms) + // If MaxOutOfOrderness = 1 second, this data should be processable + t.Log("Phase Two: Send delayed data (event time within the first window, delay < 1 second)") + lateDataTimes := []int64{500, 700, 900} // Event time of delayed data (relative to baseTime) for i, lateTime := range lateDataTimes { eventTime := baseTime + lateTime ssql.Emit(map[string]any{ "deviceId": "sensor001", "eventTime": eventTime, - "temperature": float64(20 + i), // 20-22,用于标识延迟数据 + "temperature": float64(20 + i), // 20-22, used to identify delayed data }) time.Sleep(100 * time.Millisecond) } - // 第三阶段:发送更多正常数据,推进 watermark - // 关键:要触发窗口,需要 watermark >= windowEnd + // Phase Three: Send more normal data and advance the watermark + // Key: To trigger the window, watermark > = windowEnd // watermark = maxEventTime - maxOutOfOrderness - // 所以需要:maxEventTime >= windowEnd + maxOutOfOrderness - windowSizeMs := int64(2000) // 2秒 - maxOutOfOrdernessMs := int64(1000) // 1秒 + // So you need: maxEventTime > = windowEnd + maxOutOfOrderness + windowSizeMs := int64(2000) // 2 seconds + maxOutOfOrdernessMs := int64(1000) // 1 second firstWindowEnd := baseTime + windowSizeMs requiredEventTimeForTrigger := firstWindowEnd + maxOutOfOrdernessMs - t.Log("第三阶段:继续发送正常数据,推进 watermark") + t.Log("Phase Three: Continue sending normal data and advance the watermark") for i := 10; i < 15; i++ { eventTime := baseTime + int64(i*200) - // 确保至少有一个数据的事件时间 >= requiredEventTimeForTrigger + // Make sure the event time for at least one data point > = requiredEventTimeForTrigger if i == 10 && eventTime < requiredEventTimeForTrigger { eventTime = requiredEventTimeForTrigger } @@ -1089,10 +1089,10 @@ func TestSQLSlidingWindow_MaxOutOfOrderness(t *testing.T) { time.Sleep(50 * time.Millisecond) } - // 等待窗口触发和延迟数据处理 + // Wait for window triggers and delay data processing time.Sleep(3 * time.Second) - // 收集所有窗口结果(添加超时和最大迭代次数限制) + // Collect all window results (add timeout and maximum iteration limits) ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() maxIterations := 20 @@ -1102,16 +1102,16 @@ func TestSQLSlidingWindow_MaxOutOfOrderness(t *testing.T) { select { case result, ok := <-ch: if !ok { - // channel 已关闭 + // The channel has been closed goto END } - _ = result // 使用结果 + _ = result // Results of use iteration++ case <-time.After(500 * time.Millisecond): - // 500ms 没有新结果,退出 + // Exit after 500 ms without a new result goto END case <-ctx.Done(): - // 超时退出 + // Exiting after the time limit goto END } } @@ -1125,8 +1125,8 @@ END: require.Greater(t, windowResultsLen, 0, "应该至少触发一个窗口") - // 验证窗口数据 - // 如果 MaxOutOfOrderness 配置正确,延迟数据应该能被统计到对应窗口 + // Validate window data + // If MaxOutOfOrderness is configured correctly, latency data should be counted into the corresponding window if windowResultsLen > 0 { // Sliding windows fire out of order under parallel load; aggregate // across all fired windows instead of asserting the first received. @@ -1157,11 +1157,11 @@ END: } } - t.Logf("总共触发了 %d 个窗口", windowResultsLen) + t.Logf("A total of %d windows were triggered", windowResultsLen) } -// TestSQLSlidingWindow_AllowedLateness 测试滑动窗口的 AllowedLateness 配置 -// 验证窗口触发后,延迟数据能否在允许的延迟时间内更新窗口结果 +// TestSQLSlidingWindow_AllowedLateness Test the AllowedLateness configuration for the sliding window +// After the validation window is triggered, can the delay data update the window result within the allowable delay time? func TestSQLSlidingWindow_AllowedLateness(t *testing.T) { t.Parallel() ssql := streamsql.New() @@ -1186,7 +1186,7 @@ func TestSQLSlidingWindow_AllowedLateness(t *testing.T) { ssql.AddSink(func(results []map[string]any) { defer func() { if r := recover(); r != nil { - // channel 已关闭,忽略错误 + // channel is closed, ignoring errors } }() if len(results) > 0 { @@ -1197,13 +1197,13 @@ func TestSQLSlidingWindow_AllowedLateness(t *testing.T) { } }) - // 模拟 AllowedLateness 场景 - // 场景:窗口触发后,发送延迟数据,验证窗口能否更新 - baseTime := time.Now().UnixMilli() - 10000 // 使用10秒前作为基准 + // Simulates the AllowedLateness scenario + // Scenario: After the window is triggered, delay data is sent to verify whether the window can be updated + baseTime := time.Now().UnixMilli() - 10000 // Use 10 seconds before as the baseline - // 第一阶段:发送正常顺序的数据,触发第一个窗口 - // 事件时间:0ms, 200ms, 400ms, ..., 2000ms(第一个窗口 [0ms, 2000ms)) - t.Log("第一阶段:发送正常顺序的数据(事件时间 0-2000ms)") + // Stage One: Send data in normal order to trigger the first window + // Event time: 0ms, 200ms, 400ms,..., 2000ms (first window [0ms, 2000ms))) + t.Log("Phase One: Send data in normal order (event time 0-2000ms)") for i := 0; i < 10; i++ { eventTime := baseTime + int64(i*200) ssql.Emit(map[string]any{ @@ -1214,26 +1214,26 @@ func TestSQLSlidingWindow_AllowedLateness(t *testing.T) { time.Sleep(50 * time.Millisecond) } - // 推进watermark,触发第一个窗口 - // 关键:要触发窗口,需要 watermark >= windowEnd + // Push the watermark to trigger the first window + // Key: To trigger the window, watermark > = windowEnd // watermark = maxEventTime - maxOutOfOrderness - // 所以需要:maxEventTime >= windowEnd + maxOutOfOrderness - windowSizeMs := int64(2000) // 2秒 - maxOutOfOrdernessMs := int64(1000) // 1秒 + // So you need: maxEventTime > = windowEnd + maxOutOfOrderness + windowSizeMs := int64(2000) // 2 seconds + maxOutOfOrdernessMs := int64(1000) // 1 second firstWindowEnd := baseTime + windowSizeMs requiredEventTimeForTrigger := firstWindowEnd + maxOutOfOrdernessMs - // 发送事件时间 >= requiredEventTimeForTrigger 的数据,确保 watermark >= windowEnd + // Send event time > = requiredEventTimeForTrigger data, ensuring watermark > = windowEnd ssql.Emit(map[string]any{ "deviceId": "sensor001", "eventTime": requiredEventTimeForTrigger, "temperature": 100.0, }) - // 等待 watermark 推进,触发第一个窗口 - t.Log("等待 watermark 推进,触发第一个窗口") + // Wait for the watermark to advance, triggering the first window + t.Log("Wait for watermark to advance and trigger the first window") time.Sleep(3 * time.Second) - // 收集第一个窗口的结果(添加最大迭代次数限制) + // Collect the results of the first window (add a maximum iteration limit) firstWindowReceived := false firstWindowCnt := 0.0 firstWindowMax := 0.0 @@ -1246,49 +1246,49 @@ func TestSQLSlidingWindow_AllowedLateness(t *testing.T) { select { case res, ok := <-ch: if !ok { - // channel 已关闭 + // The channel has been closed goto COLLECT_FIRST_WINDOW_END } if len(res) > 0 { firstWindowReceived = true firstWindowCnt = res[0]["cnt"].(float64) firstWindowMax = res[0]["max_temp"].(float64) - t.Logf("第一个窗口: cnt=%.0f, max=%.0f", firstWindowCnt, firstWindowMax) + t.Logf("First window: cnt = %.0f, max = %.0f", firstWindowCnt, firstWindowMax) } iteration++ case <-time.After(500 * time.Millisecond): - // 500ms 没有新结果 + // No new results for 500ms iteration++ case <-ctx.Done(): - t.Log("等待第一个窗口超时") + t.Log("Wait for the first window to time out") goto COLLECT_FIRST_WINDOW_END } } COLLECT_FIRST_WINDOW_END: if !firstWindowReceived { - t.Log("⚠ 第一个窗口未触发,可能watermark未推进到足够位置") + t.Log("⚠ The first window was not triggered, possibly because watermark was not advanced enough to a sufficient position") } assert.GreaterOrEqual(t, firstWindowCnt, 5.0, "第一个窗口应该包含足够的数据") assert.LessOrEqual(t, firstWindowMax, 9.0, "第一个窗口的最大值应该不超过9(正常数据)") - // 第二阶段:发送延迟数据(事件时间在第一个窗口内) - // 这些数据应该在 AllowedLateness = 500ms 内被处理 - t.Log("第二阶段:发送延迟数据(事件时间在第一个窗口内)") - lateDataTimes := []int64{300, 600, 900} // 延迟数据的事件时间 + // Stage Two: Sending Delayed Data (Event Time in the First Window) + // This data should be processed within AllowedLateness = 500ms + t.Log("Stage Two: Sending Delayed Data (Event Time in the First Window)") + lateDataTimes := []int64{300, 600, 900} // Delayed data event time lateDataTemps := []float64{30.0, 31.0, 32.0} for i, lateTime := range lateDataTimes { eventTime := baseTime + lateTime ssql.Emit(map[string]any{ "deviceId": "sensor001", "eventTime": eventTime, - "temperature": lateDataTemps[i], // 30-32,用于标识延迟数据 + "temperature": lateDataTemps[i], // 30-32, used to identify delay data }) time.Sleep(100 * time.Millisecond) } - // 第三阶段:继续发送正常数据,推进 watermark - t.Log("第三阶段:继续发送正常数据,推进 watermark") + // Phase Three: Continue sending normal data and advance the watermark + t.Log("Phase Three: Continue sending normal data and advance the watermark") for i := 10; i < 15; i++ { eventTime := baseTime + int64(i*200) ssql.Emit(map[string]any{ @@ -1299,10 +1299,10 @@ COLLECT_FIRST_WINDOW_END: time.Sleep(50 * time.Millisecond) } - // 等待窗口触发和延迟数据处理 + // Wait for window triggers and delay data processing time.Sleep(3 * time.Second) - // 收集所有窗口结果(添加超时和最大迭代次数限制) + // Collect all window results (add timeout and maximum iteration limits) ctx2, cancel2 := context.WithTimeout(context.Background(), 5*time.Second) defer cancel2() maxIterations2 := 20 @@ -1312,16 +1312,16 @@ COLLECT_FIRST_WINDOW_END: select { case result, ok := <-ch: if !ok { - // channel 已关闭 + // The channel has been closed goto END } - _ = result // 使用结果 + _ = result // Results of use iteration2++ case <-time.After(500 * time.Millisecond): - // 500ms 没有新结果,退出 + // Exit after 500 ms without a new result goto END case <-ctx2.Done(): - // 超时退出 + // Exiting after the time limit goto END } } @@ -1335,11 +1335,11 @@ END: require.Greater(t, windowResultsLen, 0, "应该至少触发一个窗口") - // 验证窗口数据 - // 如果 AllowedLateness 配置正确,延迟数据应该能触发窗口的延迟更新 + // Validate window data + // If AllowedLateness is configured correctly, the latency data should trigger delay updates for the window if windowResultsLen > 0 { - // 滑动窗口的延迟更新可能体现在后续的窗口结果中 - // 检查所有窗口结果,看是否有包含延迟数据的窗口 + // Delayed updates for sliding windows may be reflected in subsequent window results + // Check all window results to see if there are any windows containing delay data hasLateDataUpdate := false for i, window := range windowResultsCopy { if len(window) > 0 { @@ -1347,20 +1347,20 @@ END: minTemp := window[0]["min_temp"].(float64) maxTemp := window[0]["max_temp"].(float64) - t.Logf("窗口 %d: cnt=%.0f, min=%.0f, max=%.0f", i+1, cnt, minTemp, maxTemp) + t.Logf("Window %d: cnt=%.0f, min=%.0f, max=%.0f", i+1, cnt, minTemp, maxTemp) - // 验证窗口包含数据 + // The validation window contains data assert.GreaterOrEqual(t, cnt, 1.0, "窗口 %d 应该包含数据", i+1) - // 如果 AllowedLateness 配置正确,延迟数据应该被处理 - // 延迟数据(temperature=30-32)应该能被统计 + // If AllowedLateness is configured correctly, latency data should be handled + // Latency data (temperature=30-32) should be able to be counted if maxTemp >= 30.0 { hasLateDataUpdate = true - t.Logf("✓ 窗口 %d 包含延迟数据,最大值: %.0f", i+1, maxTemp) + t.Logf("✓ Window %d contains latency data, maximum: %.0f", i+1, maxTemp) - // 验证延迟更新包含更多数据 + // Verification delays update with more data if i == 0 { - // 第一个窗口的延迟更新应该包含更多数据 + // The delayed update for the first window should include more data assert.GreaterOrEqual(t, cnt, firstWindowCnt+3.0, "延迟更新应该包含更多数据(原数据 + 延迟数据)") } @@ -1368,22 +1368,22 @@ END: } } - // 验证是否有延迟更新(窗口可能触发多次) + // Verify for delayed updates (windows may trigger multiple times) if windowResultsLen > 1 { - t.Logf("✓ 滑动窗口触发了 %d 次,可能包含延迟更新", windowResultsLen) + t.Logf("✓ The sliding window has triggered %d times, possibly due to delayed updates", windowResultsLen) } if !hasLateDataUpdate { - t.Logf("⚠ 提示:延迟数据可能未被统计,或延迟数据的时间不在窗口范围内") + t.Logf("⚠ Note: Delayed data may not be counted, or the delay data may not be within the window range") } else { - t.Logf("✓ AllowedLateness 功能正常工作,延迟数据已被处理") + t.Logf("✓ AllowedLateness Functions are working properly, and latency data has been processed") } } - t.Logf("总共触发了 %d 个窗口", windowResultsLen) + t.Logf("A total of %d windows were triggered", windowResultsLen) } -// TestSQLSlidingWindow_EventTimeWindowAlignment 测试事件时间滑动窗口对齐到epoch +// TestSQLSlidingWindow_EventTimeWindowAlignment Test event time sliding window alignment to epoch func TestSQLSlidingWindow_EventTimeWindowAlignment(t *testing.T) { t.Parallel() ssql := streamsql.New() @@ -1408,7 +1408,7 @@ func TestSQLSlidingWindow_EventTimeWindowAlignment(t *testing.T) { ssql.AddSink(func(results []map[string]any) { defer func() { if r := recover(); r != nil { - // channel 已关闭,忽略错误 + // channel is closed, ignoring errors } }() if len(results) > 0 { @@ -1419,11 +1419,11 @@ func TestSQLSlidingWindow_EventTimeWindowAlignment(t *testing.T) { } }) - // 使用事件时间:发送数据,验证滑动窗口对齐到滑动步长 - // 窗口大小2秒,滑动步长500ms,应该对齐到500ms的倍数 + // Using event time: Send data to verify that the sliding window is aligned with the sliding step size + // The window size is 2 seconds, the sliding step length is 500ms, and it should be aligned to a multiple of 500ms baseTime := time.Now().UnixMilli() - // 发送数据,事件时间从baseTime开始,每200ms一条 + // When sending data, the event time starts from baseTime, with one message every 200ms for i := 0; i < 20; i++ { eventTime := baseTime + int64(i*200) ssql.Emit(map[string]any{ @@ -1434,14 +1434,14 @@ func TestSQLSlidingWindow_EventTimeWindowAlignment(t *testing.T) { time.Sleep(50 * time.Millisecond) } - // 等待窗口触发 + // Wait for the window to trigger time.Sleep(3 * time.Second) timeout := time.After(2 * time.Second) for { select { case <-ch: - // 继续收集结果 + // Continue collecting results case <-timeout: goto END } @@ -1456,8 +1456,8 @@ END: require.Greater(t, windowResultsLen, 0, "应该至少触发一个窗口") - // 验证窗口对齐 - windowSizeMs := int64(2000) // 2秒 + // Verify window alignment + windowSizeMs := int64(2000) // 2 seconds slideSizeMs := int64(500) // 500ms for i, window := range windowResultsCopy { if len(window) > 0 { @@ -1483,12 +1483,12 @@ END: i+1, prevStartMs, startMs, actualSlideMs) } - t.Logf("窗口 %d: start=%d, end=%d, size=%dms", i+1, startMs, endMs, endMs-startMs) + t.Logf("Window %d: start=%d, end=%d, size=%dms", i+1, startMs, endMs, endMs-startMs) } } } -// TestSQLSlidingWindow_WatermarkTriggerTiming 测试滑动窗口Watermark触发时机 +// TestSQLSlidingWindow_WatermarkTriggerTiming Test the trigger timing of the sliding window Watermark func TestSQLSlidingWindow_WatermarkTriggerTiming(t *testing.T) { t.Parallel() ssql := streamsql.New() @@ -1513,7 +1513,7 @@ func TestSQLSlidingWindow_WatermarkTriggerTiming(t *testing.T) { ssql.AddSink(func(results []map[string]any) { defer func() { if r := recover(); r != nil { - // channel 已关闭,忽略错误 + // channel is closed, ignoring errors } }() if len(results) > 0 { @@ -1524,13 +1524,13 @@ func TestSQLSlidingWindow_WatermarkTriggerTiming(t *testing.T) { } }) - // 使用事件时间:发送数据,验证watermark触发时机 + // Use event time: Send data to verify the timing of watermark trigger baseTime := time.Now().UnixMilli() - 10000 maxOutOfOrdernessMs := int64(500) slideSizeMs := int64(500) - // 发送数据,事件时间在第一个窗口内 - // 注意:第一个数据的事件时间会影响窗口对齐 + // Send data, event time within the first window + // Note: The event time of the first data point will affect window alignment firstEventTime := baseTime for i := 0; i < 10; i++ { eventTime := baseTime + int64(i*200) @@ -1542,30 +1542,30 @@ func TestSQLSlidingWindow_WatermarkTriggerTiming(t *testing.T) { time.Sleep(50 * time.Millisecond) } - // 计算对齐后的第一个窗口开始时间(基于第一个数据的事件时间) - // alignWindowStart 会对齐到小于等于事件时间的最大对齐点 + // Calculate the start time of the first window after alignment (event time based on the first data) + // alignWindowStart will align to the maximum alignment point less than or equal to the event time alignedStart := (firstEventTime / slideSizeMs) * slideSizeMs - firstWindowEnd := alignedStart + 2000 // 窗口大小2秒 + firstWindowEnd := alignedStart + 2000 // Window size is 2 seconds - t.Logf("第一个窗口: [%d, %d)", alignedStart, firstWindowEnd) + t.Logf("First window: [%d, %d)", alignedStart, firstWindowEnd) - // 发送一个事件时间超过window_end的数据,推进watermark + // Send data that has an event time longer than window_end and advance the watermark // watermark = maxEventTime - maxOutOfOrderness = (firstWindowEnd + 1000) - 500 = firstWindowEnd + 500 - // 此时 watermark >= firstWindowEnd,窗口应该触发 + // At this point, watermark > = firstWindowEnd, the window should be triggered ssql.Emit(map[string]any{ "deviceId": "sensor001", "eventTime": firstWindowEnd + 1000, "temperature": 200.0, }) - // 等待窗口触发 + // Wait for the window to trigger time.Sleep(1 * time.Second) timeout := time.After(2 * time.Second) for { select { case <-ch: - // 继续收集结果 + // Continue collecting results case <-timeout: goto END } @@ -1580,7 +1580,7 @@ END: require.Greater(t, windowResultsLen, 0, "应该至少触发一个窗口") - // 验证第一个窗口的触发时机 + // Verify the timing of the first window trigger if windowResultsLen > 0 { firstWindow := windowResultsCopy[0] if len(firstWindow) > 0 { @@ -1591,20 +1591,20 @@ END: startMs := start / int64(time.Millisecond) endMs := end / int64(time.Millisecond) - // 验证窗口对齐到滑动步长(允许一定的误差,因为对齐基于第一个数据的事件时间) + // Validation window alignment to sliding step length (allowing some error, since alignment is based on the event time of the first data) assert.Equal(t, int64(0), startMs%slideSizeMs, "第一个窗口的开始时间应该对齐到滑动步长,expected对齐到%d的倍数,actual=%d", slideSizeMs, startMs) - // 验证窗口大小正确 + // Verify that the window size is correct assert.Equal(t, int64(2000), endMs-startMs, "第一个窗口的大小应该是2秒(2000ms),actual=%d", endMs-startMs) - t.Logf("✓ 滑动窗口在watermark >= window_end时正确触发") - t.Logf("窗口: [%d, %d), 触发时maxEventTime >= %d", start, end, end+maxOutOfOrdernessMs) + t.Logf("✓ Sliding window triggers correctly when watermark > = window_end") + t.Logf("Window: [%d, %d), triggered maxEventTime > = %d", start, end, end+maxOutOfOrdernessMs) } } } -// TestSQLSlidingWindow_IdleSourceMechanism 测试滑动窗口的Idle Source机制 +// TestSQLSlidingWindow_IdleSourceMechanism Test the Idle Source mechanism of the slider window func TestSQLSlidingWindow_IdleSourceMechanism(t *testing.T) { t.Parallel() ssql := streamsql.New() @@ -1629,7 +1629,7 @@ func TestSQLSlidingWindow_IdleSourceMechanism(t *testing.T) { ssql.AddSink(func(results []map[string]any) { defer func() { if r := recover(); r != nil { - // channel 已关闭,忽略错误 + // channel is closed, ignoring errors } }() if len(results) > 0 { @@ -1644,8 +1644,8 @@ func TestSQLSlidingWindow_IdleSourceMechanism(t *testing.T) { slideSizeMs := int64(500) alignedStart := (baseTime / slideSizeMs) * slideSizeMs - // 发送数据 - t.Log("发送数据,创建滑动窗口") + // Send data + t.Log("Send data and create a sliding window") for i := 0; i < 10; i++ { eventTime := alignedStart + int64(i*200) ssql.Emit(map[string]any{ @@ -1656,16 +1656,16 @@ func TestSQLSlidingWindow_IdleSourceMechanism(t *testing.T) { time.Sleep(50 * time.Millisecond) } - // 停止发送数据,等待Idle Source机制触发 - t.Log("停止发送数据,等待Idle Source机制触发(IdleTimeout=2s)") + // Stop sending data and wait for the Idle Source mechanism to trigger + t.Log("Stop sending data and wait for the Idle Source mechanism to trigger (IdleTimeout=2s)") time.Sleep(3 * time.Second) - // 收集窗口结果 + // Collect window results timeout := time.After(3 * time.Second) for { select { case <-ch: - // 继续收集结果 + // Continue collecting results case <-timeout: goto END } @@ -1681,14 +1681,14 @@ END: require.Greater(t, windowResultsLen, 0, "应该至少触发一个滑动窗口(即使数据源空闲)") if windowResultsLen > 0 { - t.Logf("✓ 滑动窗口Idle Source机制正常工作,触发了 %d 个窗口", windowResultsLen) + t.Logf("✓ The sliding window Idle Source mechanism works normally, triggering %d windows", windowResultsLen) for i, window := range windowResultsCopy { if len(window) > 0 { row := window[0] start := row["start"].(int64) end := row["end"].(int64) cnt := row["cnt"].(float64) - t.Logf("窗口 %d: [%d, %d), cnt=%.0f", i+1, start, end, cnt) + t.Logf("Window %d: [%d, %d), cnt=%.0f", i+1, start, end, cnt) } } } diff --git a/test/e2e/window_tumbling_test.go b/test/e2e/window_tumbling_test.go index 2734e3d..3a14378 100644 --- a/test/e2e/window_tumbling_test.go +++ b/test/e2e/window_tumbling_test.go @@ -11,8 +11,8 @@ import ( "github.com/stretchr/testify/require" ) -// TestSQLTumblingWindow_ProcessingTime 测试处理时间的滚动窗口 -// 验证不使用 WITH 子句时,滚动窗口基于处理时间(系统时钟)工作 +// TestSQLTumblingWindow_ProcessingTime Rolling window for test processing times +// When verification does not use the WITH clause, the scroll window operates based on processing time (system clock). func TestSQLTumblingWindow_ProcessingTime(t *testing.T) { t.Parallel() ssql := streamsql.New() @@ -42,22 +42,22 @@ func TestSQLTumblingWindow_ProcessingTime(t *testing.T) { select { case ch <- results: default: - // 非阻塞发送 + // Non-blocking transmission } } }) - // 使用处理时间:发送数据,不包含时间戳字段 - // 滚动窗口基于数据到达的处理时间(系统时钟)来划分窗口 + // Use processing time: Send data without timestamp fields + // Scrolling windows are divided based on the processing time of data arrival (system clock). for i := 0; i < 10; i++ { ssql.Emit(map[string]any{ "deviceId": "sensor001", "temperature": float64(i), }) - time.Sleep(200 * time.Millisecond) // 每200ms发送一条数据 + time.Sleep(200 * time.Millisecond) // Send a data piece every 200ms } - // 等待窗口触发(处理时间滚动窗口基于系统时钟触发) + // Wait window trigger (processing time rolling window triggered based on system clock) time.Sleep(3 * time.Second) ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) @@ -69,16 +69,16 @@ func TestSQLTumblingWindow_ProcessingTime(t *testing.T) { select { case result, ok := <-ch: if !ok { - // channel 已关闭 + // The channel has been closed goto END } - _ = result // 使用结果 + _ = result // Results of use iteration++ case <-time.After(500 * time.Millisecond): - // 500ms 没有新结果,退出 + // Exit after 500 ms without a new result goto END case <-ctx.Done(): - // 超时退出 + // Exiting after the time limit goto END } } @@ -106,19 +106,19 @@ END: assert.LessOrEqual(t, minTemp, avgTemp, "最小值应该小于等于平均值") assert.LessOrEqual(t, avgTemp, maxTemp, "平均值应该小于等于最大值") - t.Logf("处理时间滚动窗口成功触发,数据量: %.0f, 平均温度: %.2f", cnt, avgTemp) + t.Logf("The processing time scrolling window successfully triggered, data volume: %.0f, average temperature: %.2f", cnt, avgTemp) } } } -// TestSQLTumblingWindow_MaxOutOfOrderness 测试滚动窗口的最大延迟时间配置 -// 验证设置 MaxOutOfOrderness 后,延迟数据能否在允许的延迟范围内被正确处理 +// TestSQLTumblingWindow_MaxOutOfOrderness Test the maximum latency configuration for the rolling window +// After verifying whether the MaxOutOfOrderness setting is properly processed within the allowable latency range, func TestSQLTumblingWindow_MaxOutOfOrderness(t *testing.T) { t.Parallel() ssql := streamsql.New() defer ssql.Stop() - // 使用 SQL 配置 MaxOutOfOrderness + // Configure MaxOutOfOrderness using SQL sql := ` SELECT deviceId, COUNT(*) as cnt, @@ -143,17 +143,17 @@ func TestSQLTumblingWindow_MaxOutOfOrderness(t *testing.T) { } }) - // 模拟延迟数据场景 - // 场景:设置 MaxOutOfOrderness = 1秒,测试延迟数据能否在1秒内被正确处理 - // 窗口大小2秒,需要对齐到2秒的倍数 - windowSizeMs := int64(2000) // 2秒 - baseTimeRaw := time.Now().UnixMilli() - 10000 // 使用10秒前作为基准 - // 对齐baseTime到窗口大小的倍数,确保窗口对齐行为可预测 + // Simulating latency data scenarios + // Scenario: Set MaxOutOfOrderness = 1 second to test whether latency data is correctly processed within 1 second + // The window size is 2 seconds, and it needs to be aligned to a multiple of 2 seconds + windowSizeMs := int64(2000) // 2 seconds + baseTimeRaw := time.Now().UnixMilli() - 10000 // Use 10 seconds before as the baseline + // Align baseTime as a multiple to window size to ensure window alignment behavior is predictable baseTime := (baseTimeRaw / windowSizeMs) * windowSizeMs - // 第一阶段:发送正常顺序的数据 - // 事件时间:0ms, 200ms, 400ms, ..., 2000ms(第一个窗口 [0ms, 2000ms)) - t.Log("第一阶段:发送正常顺序的数据(事件时间 0-2000ms)") + // Stage One: Send data in normal order + // Event time: 0ms, 200ms, 400ms,..., 2000ms (first window [0ms, 2000ms))) + t.Log("Phase One: Send data in normal order (event time 0-2000ms)") for i := 0; i < 10; i++ { eventTime := baseTime + int64(i*200) ssql.Emit(map[string]any{ @@ -164,27 +164,27 @@ func TestSQLTumblingWindow_MaxOutOfOrderness(t *testing.T) { time.Sleep(50 * time.Millisecond) } - // 等待 watermark 推进,触发第一个窗口 - t.Log("等待 watermark 推进,触发第一个窗口") + // Wait for the watermark to advance, triggering the first window + t.Log("Wait for watermark to advance and trigger the first window") time.Sleep(3 * time.Second) - // 第二阶段:发送延迟数据 - // 延迟数据的事件时间在第一个窗口内(如 500ms, 700ms, 900ms) - // 如果 MaxOutOfOrderness = 1秒,这些数据应该能被处理 - t.Log("第二阶段:发送延迟数据(事件时间在第一个窗口内,延迟 < 1秒)") - lateDataTimes := []int64{500, 700, 900} // 延迟数据的事件时间(相对于 baseTime) + // Stage Two: Send delayed data + // The event time of the delayed data occurs within the first window (e.g., 500ms, 700ms, 900ms) + // If MaxOutOfOrderness = 1 second, this data should be processable + t.Log("Phase Two: Send delayed data (event time within the first window, delay < 1 second)") + lateDataTimes := []int64{500, 700, 900} // Event time of delayed data (relative to baseTime) for i, lateTime := range lateDataTimes { eventTime := baseTime + lateTime ssql.Emit(map[string]any{ "deviceId": "sensor001", "eventTime": eventTime, - "temperature": float64(20 + i), // 20-22,用于标识延迟数据 + "temperature": float64(20 + i), // 20-22, used to identify delayed data }) time.Sleep(100 * time.Millisecond) } - // 第三阶段:发送更多正常数据,推进 watermark - t.Log("第三阶段:继续发送正常数据,推进 watermark") + // Phase Three: Send more normal data and advance the watermark + t.Log("Phase Three: Continue sending normal data and advance the watermark") for i := 10; i < 15; i++ { eventTime := baseTime + int64(i*200) ssql.Emit(map[string]any{ @@ -195,10 +195,10 @@ func TestSQLTumblingWindow_MaxOutOfOrderness(t *testing.T) { time.Sleep(50 * time.Millisecond) } - // 等待窗口触发和延迟数据处理 + // Wait for window triggers and delay data processing time.Sleep(3 * time.Second) - // 收集所有窗口结果 + // Collect all window results ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() maxIterations := 20 @@ -208,16 +208,16 @@ func TestSQLTumblingWindow_MaxOutOfOrderness(t *testing.T) { select { case result, ok := <-ch: if !ok { - // channel 已关闭 + // The channel has been closed goto END } - _ = result // 使用结果 + _ = result // Results of use iteration++ case <-time.After(500 * time.Millisecond): - // 500ms 没有新结果,退出 + // Exit after 500 ms without a new result goto END case <-ctx.Done(): - // 超时退出 + // Exiting after the time limit goto END } } @@ -231,8 +231,8 @@ END: require.Greater(t, windowResultsLen, 0, "应该至少触发一个窗口") - // 验证窗口数据 - // 如果 MaxOutOfOrderness 配置正确,延迟数据应该能被统计到对应窗口 + // Validate window data + // If MaxOutOfOrderness is configured correctly, latency data should be counted into the corresponding window if windowResultsLen > 0 { firstWindow := windowResultsCopy[0] if len(firstWindow) > 0 { @@ -240,28 +240,28 @@ END: minTemp := firstWindow[0]["min_temp"].(float64) maxTemp := firstWindow[0]["max_temp"].(float64) - t.Logf("第一个窗口: cnt=%.0f, min=%.0f, max=%.0f", cnt, minTemp, maxTemp) + t.Logf("First window: cnt=%.0f, min=%.0f, max=%.0f", cnt, minTemp, maxTemp) - // 验证窗口包含数据 - // 滚动窗口:窗口大小2秒,每200ms一条数据,理论上应该有10条数据 - // 但由于窗口对齐和 watermark 机制,实际数据量可能略有不同 + // The validation window contains data + // Scrolling window: window size is 2 seconds, one data entry every 200ms, theoretically there should be 10 data entries + // However, due to window alignment and watermark mechanisms, the actual data volume may vary slightly assert.GreaterOrEqual(t, cnt, 3.0, "第一个窗口应该包含足够的数据(滚动窗口特性)") assert.Equal(t, 0.0, minTemp, "第一个窗口的最小值应该是0(正常数据)") - // 如果 MaxOutOfOrderness 配置正确,延迟数据应该被处理 + // If MaxOutOfOrderness is configured correctly, latency data should be handled if maxTemp >= 20.0 { - t.Logf("✓ 延迟数据被正确处理,最大值包含延迟数据: %.0f", maxTemp) + t.Logf("✓ Delay data is handled correctly, with maximum value including delay data: %.0f", maxTemp) } else { - t.Logf("提示:延迟数据可能未被统计,当前最大值: %.0f", maxTemp) + t.Logf("Note: Latency data may not be counted; current maximum value: %.0f", maxTemp) } } } - t.Logf("总共触发了 %d 个窗口", windowResultsLen) + t.Logf("A total of %d windows were triggered", windowResultsLen) } -// TestSQLTumblingWindow_AllowedLateness 测试滚动窗口的 AllowedLateness 配置 -// 验证窗口触发后,延迟数据能否在允许的延迟时间内更新窗口结果 +// TestSQLTumblingWindow_AllowedLateness Test the AllowedLateness configuration for the scrolling window +// After the validation window is triggered, can the delay data update the window result within the allowable delay time? func TestSQLTumblingWindow_AllowedLateness(t *testing.T) { t.Parallel() ssql := streamsql.New() @@ -291,13 +291,13 @@ func TestSQLTumblingWindow_AllowedLateness(t *testing.T) { } }) - // 模拟 AllowedLateness 场景 - // 场景:窗口触发后,发送延迟数据,验证窗口能否更新 - baseTime := time.Now().UnixMilli() - 10000 // 使用10秒前作为基准 + // Simulates the AllowedLateness scenario + // Scenario: After the window is triggered, delay data is sent to verify whether the window can be updated + baseTime := time.Now().UnixMilli() - 10000 // Use 10 seconds before as the baseline - // 第一阶段:发送正常顺序的数据,触发第一个窗口 - // 事件时间:0ms, 200ms, 400ms, ..., 2000ms(第一个窗口 [0ms, 2000ms)) - t.Log("第一阶段:发送正常顺序的数据(事件时间 0-2000ms)") + // Stage One: Send data in normal order to trigger the first window + // Event time: 0ms, 200ms, 400ms,..., 2000ms (first window [0ms, 2000ms))) + t.Log("Phase One: Send data in normal order (event time 0-2000ms)") for i := 0; i < 10; i++ { eventTime := baseTime + int64(i*200) ssql.Emit(map[string]any{ @@ -308,26 +308,26 @@ func TestSQLTumblingWindow_AllowedLateness(t *testing.T) { time.Sleep(50 * time.Millisecond) } - // 等待 watermark 推进,触发第一个窗口 - t.Log("等待 watermark 推进,触发第一个窗口") + // Wait for the watermark to advance, triggering the first window + t.Log("Wait for watermark to advance and trigger the first window") time.Sleep(3 * time.Second) - // 第二阶段:发送延迟数据(事件时间在第一个窗口内) - // 这些数据应该在 AllowedLateness = 1秒 内被处理 - t.Log("第二阶段:发送延迟数据(事件时间在第一个窗口内)") - lateDataTimes := []int64{300, 600, 900} // 延迟数据的事件时间 + // Stage Two: Sending Delayed Data (Event Time in the First Window) + // This data should be processed within AllowedLateness = 1 second + t.Log("Stage Two: Sending Delayed Data (Event Time in the First Window)") + lateDataTimes := []int64{300, 600, 900} // Delayed data event time for i, lateTime := range lateDataTimes { eventTime := baseTime + lateTime ssql.Emit(map[string]any{ "deviceId": "sensor001", "eventTime": eventTime, - "temperature": float64(30 + i), // 30-32,用于标识延迟数据 + "temperature": float64(30 + i), // 30-32, used to identify delay data }) time.Sleep(100 * time.Millisecond) } - // 第三阶段:继续发送正常数据,推进 watermark - t.Log("第三阶段:继续发送正常数据,推进 watermark") + // Phase Three: Continue sending normal data and advance the watermark + t.Log("Phase Three: Continue sending normal data and advance the watermark") for i := 10; i < 15; i++ { eventTime := baseTime + int64(i*200) ssql.Emit(map[string]any{ @@ -338,10 +338,10 @@ func TestSQLTumblingWindow_AllowedLateness(t *testing.T) { time.Sleep(50 * time.Millisecond) } - // 等待窗口触发和延迟数据处理 + // Wait for window triggers and delay data processing time.Sleep(3 * time.Second) - // 收集所有窗口结果 + // Collect all window results ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() maxIterations := 20 @@ -351,16 +351,16 @@ func TestSQLTumblingWindow_AllowedLateness(t *testing.T) { select { case result, ok := <-ch: if !ok { - // channel 已关闭 + // The channel has been closed goto END } - _ = result // 使用结果 + _ = result // Results of use iteration++ case <-time.After(500 * time.Millisecond): - // 500ms 没有新结果,退出 + // Exit after 500 ms without a new result goto END case <-ctx.Done(): - // 超时退出 + // Exiting after the time limit goto END } } @@ -374,11 +374,11 @@ END: require.Greater(t, windowResultsLen, 0, "应该至少触发一个窗口") - // 验证窗口数据 - // 如果 AllowedLateness 配置正确,延迟数据应该能触发窗口的延迟更新 + // Validate window data + // If AllowedLateness is configured correctly, the latency data should trigger delay updates for the window if windowResultsLen > 0 { - // 滚动窗口的延迟更新可能体现在后续的窗口结果中 - // 检查所有窗口结果,看是否有包含延迟数据的窗口 + // Delayed updates for rolling windows may be reflected in subsequent window results + // Check all window results to see if there are any windows containing delay data hasLateData := false for i, window := range windowResultsCopy { if len(window) > 0 { @@ -386,38 +386,38 @@ END: minTemp := window[0]["min_temp"].(float64) maxTemp := window[0]["max_temp"].(float64) - t.Logf("窗口 %d: cnt=%.0f, min=%.0f, max=%.0f", i+1, cnt, minTemp, maxTemp) + t.Logf("Window %d: cnt=%.0f, min=%.0f, max=%.0f", i+1, cnt, minTemp, maxTemp) - // 验证窗口包含数据 + // The validation window contains data assert.GreaterOrEqual(t, cnt, 1.0, "窗口 %d 应该包含数据", i+1) - // 如果 AllowedLateness 配置正确,延迟数据应该被处理 - // 延迟数据(temperature=30-32)应该能被统计 + // If AllowedLateness is configured correctly, latency data should be handled + // Latency data (temperature=30-32) should be able to be counted if maxTemp >= 30.0 { hasLateData = true - t.Logf("✓ 窗口 %d 包含延迟数据,最大值: %.0f", i+1, maxTemp) + t.Logf("✓ Window %d contains latency data, maximum: %.0f", i+1, maxTemp) } } } - // 验证是否有延迟更新(窗口可能触发多次) + // Verify for delayed updates (windows may trigger multiple times) if windowResultsLen > 1 { - t.Logf("✓ 滚动窗口触发了 %d 次,可能包含延迟更新", windowResultsLen) + t.Logf("✓ The scroll window has triggered %d times, possibly including delayed updates", windowResultsLen) } if !hasLateData { - t.Logf("提示:延迟数据可能未被统计,或延迟数据的时间不在窗口范围内") + t.Logf("Note: Delayed data may not be counted, or the delay data may not be within the window range") } } - t.Logf("总共触发了 %d 个窗口", windowResultsLen) + t.Logf("A total of %d windows were triggered", windowResultsLen) } -// TestSQLTumblingWindow_BothConfigs 测试滚动窗口同时配置 MaxOutOfOrderness 和 AllowedLateness -// 验证两个配置组合使用时,延迟数据能否被正确处理 +// TestSQLTumblingWindow_BothConfigs Test the scrolling window by configuring both MaxOutOfOrderness and AllowedLateness +// Verify whether latency data is correctly handled when using two configurations in combination func TestSQLTumblingWindow_BothConfigs(t *testing.T) { t.Parallel() - // 启用调试日志(可选,用于排查问题) + // Enable debug logs (optional, for troubleshooting) // window.EnableDebug = true ssql := streamsql.New() @@ -452,21 +452,21 @@ func TestSQLTumblingWindow_BothConfigs(t *testing.T) { } }) - // 模拟完整的延迟数据处理场景 - // 关键:确保 baseTime 对齐到窗口边界,以便窗口对齐行为可预测 - windowSizeMs := int64(2000) // 2秒 + // Simulates a complete latency data processing scenario + // Key: Ensure baseTime aligns with window boundaries so window alignment behavior is predictable + windowSizeMs := int64(2000) // 2 seconds baseTimeRaw := time.Now().UnixMilli() - 10000 - baseTime := (baseTimeRaw / windowSizeMs) * windowSizeMs // 对齐到窗口边界 - maxOutOfOrdernessMs := int64(1000) // 1秒 + baseTime := (baseTimeRaw / windowSizeMs) * windowSizeMs // Aligned with the window boundary + maxOutOfOrdernessMs := int64(1000) // 1 second firstWindowEnd := baseTime + windowSizeMs - // 关键:要触发窗口,需要 watermark >= windowEnd + // Key: To trigger the window, watermark > = windowEnd // watermark = maxEventTime - maxOutOfOrderness - // 所以需要:maxEventTime - maxOutOfOrderness >= windowEnd - // 即:maxEventTime >= windowEnd + maxOutOfOrderness + // So you need: maxEventTime - maxOutOfOrderness > = windowEnd + // That is: maxEventTime > = windowEnd + maxOutOfOrderness requiredEventTimeForTrigger := firstWindowEnd + maxOutOfOrdernessMs - // 第一阶段:发送正常顺序的数据 - t.Log("第一阶段:发送正常顺序的数据") + // Stage One: Send data in normal order + t.Log("Stage One: Send data in normal order") for i := 0; i < 10; i++ { eventTime := baseTime + int64(i*200) ssql.Emit(map[string]any{ @@ -477,31 +477,31 @@ func TestSQLTumblingWindow_BothConfigs(t *testing.T) { time.Sleep(50 * time.Millisecond) } - // 等待 watermark 推进(考虑 MaxOutOfOrderness = 1s) - t.Log("等待 watermark 推进,触发窗口(MaxOutOfOrderness = 1s)") + // Wait for watermark to advance (consider MaxOutOfOrderness = 1s) + t.Log("Wait for watermark advance, trigger window (MaxOutOfOrderness = 1s)") time.Sleep(3 * time.Second) - // 第二阶段:发送延迟数据(事件时间在第一个窗口内) - // MaxOutOfOrderness = 1s:这些数据应该在允许的乱序范围内 - // AllowedLateness = 500ms:窗口触发后还能接受500ms的延迟数据 - t.Log("第二阶段:发送延迟数据(事件时间在第一个窗口内)") - lateDataTimes := []int64{400, 800, 1200} // 延迟数据的事件时间 + // Stage Two: Sending Delayed Data (Event Time in the First Window) + // MaxOutOfOrderness = 1s: This data should be within the allowable out-of-order range + // AllowedLateness = 500ms: After the window is triggered, it can still accept 500ms of latency data + t.Log("Stage Two: Sending Delayed Data (Event Time in the First Window)") + lateDataTimes := []int64{400, 800, 1200} // Delayed data event time for i, lateTime := range lateDataTimes { eventTime := baseTime + lateTime ssql.Emit(map[string]any{ "deviceId": "sensor001", "eventTime": eventTime, - "temperature": float64(40 + i), // 40-42,用于标识延迟数据 + "temperature": float64(40 + i), // 40-42, used to identify delayed data }) time.Sleep(100 * time.Millisecond) } - // 第三阶段:继续发送正常数据,推进 watermark - // 关键:必须发送事件时间 >= requiredEventTimeForTrigger 的数据,才能让 watermark >= windowEnd - t.Log("第三阶段:继续发送正常数据,推进 watermark") + // Phase Three: Continue sending normal data and advance the watermark + // Key: You must send the event time > = requiredEventTimeForTrigger data to allow watermark > = windowEnd + t.Log("Phase Three: Continue sending normal data and advance the watermark") for i := 10; i < 15; i++ { eventTime := baseTime + int64(i*200) - // 确保至少有一个数据的事件时间 >= requiredEventTimeForTrigger + // Make sure the event time for at least one data point > = requiredEventTimeForTrigger if i == 10 && eventTime < requiredEventTimeForTrigger { eventTime = requiredEventTimeForTrigger } @@ -513,10 +513,10 @@ func TestSQLTumblingWindow_BothConfigs(t *testing.T) { time.Sleep(50 * time.Millisecond) } - // 等待窗口触发和延迟数据处理 + // Wait for window triggers and delay data processing time.Sleep(3 * time.Second) - // 收集所有窗口结果 + // Collect all window results ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() maxIterations := 20 @@ -526,16 +526,16 @@ func TestSQLTumblingWindow_BothConfigs(t *testing.T) { select { case result, ok := <-ch: if !ok { - // channel 已关闭 + // The channel has been closed goto END } - _ = result // 使用结果 + _ = result // Results of use iteration++ case <-time.After(500 * time.Millisecond): - // 500ms 没有新结果,退出 + // Exit after 500 ms without a new result goto END case <-ctx.Done(): - // 超时退出 + // Exiting after the time limit goto END } } @@ -549,7 +549,7 @@ END: require.Greater(t, windowResultsLen, 0, "应该至少触发一个窗口") - // 验证窗口数据 + // Validate window data if windowResultsLen > 0 { firstWindow := windowResultsCopy[0] if len(firstWindow) > 0 { @@ -557,43 +557,43 @@ END: minTemp := firstWindow[0]["min_temp"].(float64) maxTemp := firstWindow[0]["max_temp"].(float64) - t.Logf("第一个窗口: cnt=%.0f, min=%.0f, max=%.0f", cnt, minTemp, maxTemp) + t.Logf("First window: cnt=%.0f, min=%.0f, max=%.0f", cnt, minTemp, maxTemp) - // 验证窗口包含数据 - // 滚动窗口:窗口大小2秒,每200ms一条数据,理论上应该有10条数据 - // 但由于窗口对齐和 watermark 机制,实际数据量可能略有不同 + // The validation window contains data + // Scrolling window: window size is 2 seconds, one data entry every 200ms, theoretically there should be 10 data entries + // However, due to window alignment and watermark mechanisms, the actual data volume may vary slightly assert.GreaterOrEqual(t, cnt, 3.0, "第一个窗口应该包含足够的数据(滚动窗口特性)") assert.Equal(t, 0.0, minTemp, "第一个窗口的最小值应该是0(正常数据)") - // 验证延迟数据是否被处理 - // 如果配置正确,maxTemp 可能包含延迟数据的值(40-42) + // Verify whether delayed data is being processed + // If configured correctly, maxTemp may contain latency data values (40-42) if maxTemp >= 40.0 { - t.Logf("✓ 延迟数据被正确处理,最大值包含延迟数据: %.0f", maxTemp) + t.Logf("✓ Delay data is handled correctly, with maximum value including delay data: %.0f", maxTemp) } else { - t.Logf("提示:延迟数据可能未被统计,当前最大值: %.0f", maxTemp) + t.Logf("Note: Latency data may not be counted; current maximum value: %.0f", maxTemp) } } - // 验证是否有延迟更新 + // Verify if there is a delayed update if windowResultsLen > 1 { - t.Logf("✓ 滚动窗口触发了 %d 次,可能包含延迟更新", windowResultsLen) + t.Logf("✓ The scroll window has triggered %d times, possibly including delayed updates", windowResultsLen) - // 验证后续窗口的数据 + // Verify the data in subsequent windows for i := 1; i < windowResultsLen && i < 3; i++ { if len(windowResultsCopy[i]) > 0 { cnt := windowResultsCopy[i][0]["cnt"].(float64) - t.Logf("窗口 %d: cnt=%.0f", i+1, cnt) + t.Logf("Window %d: cnt=%.0f", i+1, cnt) } } } } - t.Logf("总共触发了 %d 个窗口", windowResultsLen) - t.Logf("配置验证:MaxOutOfOrderness=1s, AllowedLateness=500ms") + t.Logf("A total of %d windows were triggered", windowResultsLen) + t.Logf("Configuration verification: MaxOutOfOrderness=1s, AllowedLateness=500ms") } -// TestSQLTumblingWindow_LateDataHandling 测试滚动窗口的延迟数据处理 -// 验证即使数据延迟到达,只要在允许的延迟范围内,也能正确统计到对应窗口 +// TestSQLTumblingWindow_LateDataHandling Test the delayed data processing of the rolling window +// Verification: Even if data arrives with delay, as long as it is within the allowable delay range, the corresponding window can still be correctly counted func TestSQLTumblingWindow_LateDataHandling(t *testing.T) { t.Parallel() ssql := streamsql.New() @@ -623,49 +623,49 @@ func TestSQLTumblingWindow_LateDataHandling(t *testing.T) { } }) - // 使用事件时间:模拟延迟数据场景 - // 场景:先发送正常顺序的数据,然后发送一些延迟的数据 - // 窗口大小2秒,需要对齐到2秒的倍数 - windowSizeMs := int64(2000) // 2秒 - baseTimeRaw := time.Now().UnixMilli() - 5000 // 使用5秒前作为基准 - // 对齐baseTime到窗口大小的倍数,确保窗口对齐行为可预测 + // Using event time: simulates latency data scenarios + // Scenario: First send data in normal order, then send some delayed data + // The window size is 2 seconds, and it needs to be aligned to a multiple of 2 seconds + windowSizeMs := int64(2000) // 2 seconds + baseTimeRaw := time.Now().UnixMilli() - 5000 // Use 5 seconds before as the baseline + // Align baseTime as a multiple to window size to ensure window alignment behavior is predictable baseTime := (baseTimeRaw / windowSizeMs) * windowSizeMs - // 第一阶段:发送正常顺序的数据(事件时间:0ms, 200ms, 400ms, ..., 2000ms) - // 这些数据应该被统计到第一个窗口 [0ms, 2000ms) - t.Log("第一阶段:发送正常顺序的数据") + // Phase One: Send data in normal order (event duration: 0ms, 200ms, 400ms,..., 2000ms) + // These data should be counted in the first window [0ms, 2000ms]. + t.Log("Stage One: Send data in normal order") for i := 0; i < 10; i++ { - eventTime := baseTime + int64(i*200) // 每200ms一条数据 + eventTime := baseTime + int64(i*200) // One data piece every 200ms ssql.Emit(map[string]any{ "deviceId": "sensor001", "eventTime": eventTime, - "temperature": float64(i), // 温度值 0-9 + "temperature": float64(i), // Temperature range: 0-9 }) - time.Sleep(50 * time.Millisecond) // 处理时间间隔较小 + time.Sleep(50 * time.Millisecond) // Processing time intervals are smaller } - // 等待 watermark 推进,让第一个窗口触发 - // 窗口大小2秒,第一个窗口应该在 watermark >= baseTime + 2000ms 时触发 - t.Log("等待 watermark 推进,触发第一个窗口") + // Wait for the watermark to advance and let the first window trigger + // The window size is 2 seconds, and the first window should be triggered when watermark > = baseTime + 2000ms + t.Log("Wait for watermark to advance and trigger the first window") time.Sleep(3 * time.Second) - // 第二阶段:发送延迟的数据 - // 这些数据的事件时间比之前的数据早,但应该在允许的延迟范围内 - // 延迟数据的事件时间:100ms, 300ms, 500ms(这些时间在第一个窗口 [0ms, 2000ms) 内) - t.Log("第二阶段:发送延迟数据(事件时间在第一个窗口内)") + // Stage Two: Send delayed data + // The event timing of these data is earlier than previous data, but it should be within the allowable delay range + // Event time for delayed data: 100ms, 300ms, 500ms (these times occur within the first window [0ms, 2000ms))') + t.Log("Stage Two: Sending Delayed Data (Event Time in the First Window)") for i := 0; i < 3; i++ { - // 延迟数据:事件时间比正常数据早,但仍在窗口范围内 + // Data delay: The event time is earlier than normal data but still within the window range eventTime := baseTime + int64(100+i*200) // 100ms, 300ms, 500ms ssql.Emit(map[string]any{ "deviceId": "sensor001", "eventTime": eventTime, - "temperature": float64(10 + i), // 温度值 10-12,用于区分延迟数据 + "temperature": float64(10 + i), // Temperature values of 10-12 are used to distinguish delayed data }) time.Sleep(100 * time.Millisecond) } - // 继续发送更多正常数据,推进 watermark - t.Log("第三阶段:继续发送正常数据,推进 watermark") + // Continue sending more normal data to advance Watermark + t.Log("Phase Three: Continue sending normal data and advance the watermark") for i := 10; i < 15; i++ { eventTime := baseTime + int64(i*200) ssql.Emit(map[string]any{ @@ -676,10 +676,10 @@ func TestSQLTumblingWindow_LateDataHandling(t *testing.T) { time.Sleep(50 * time.Millisecond) } - // 等待窗口触发和延迟数据处理 + // Wait for window triggers and delay data processing time.Sleep(3 * time.Second) - // 收集所有窗口结果 + // Collect all window results ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() maxIterations := 20 @@ -689,16 +689,16 @@ func TestSQLTumblingWindow_LateDataHandling(t *testing.T) { select { case result, ok := <-ch: if !ok { - // channel 已关闭 + // The channel has been closed goto END } - _ = result // 使用结果 + _ = result // Results of use iteration++ case <-time.After(500 * time.Millisecond): - // 500ms 没有新结果,退出 + // Exit after 500 ms without a new result goto END case <-ctx.Done(): - // 超时退出 + // Exiting after the time limit goto END } } @@ -712,8 +712,8 @@ END: require.Greater(t, windowResultsLen, 0, "应该至少触发一个窗口") - // 验证第一个窗口的数据 - // 第一个窗口应该包含正常数据(0-9)和可能的延迟数据 + // Verify the data from the first window + // The first window should contain normal data (0-9) and possible latency data if windowResultsLen > 0 { firstWindow := windowResultsCopy[0] if len(firstWindow) > 0 { @@ -721,23 +721,23 @@ END: minTemp := firstWindow[0]["min_temp"].(float64) maxTemp := firstWindow[0]["max_temp"].(float64) - t.Logf("第一个窗口: cnt=%.0f, min=%.0f, max=%.0f", cnt, minTemp, maxTemp) + t.Logf("First window: cnt=%.0f, min=%.0f, max=%.0f", cnt, minTemp, maxTemp) - // 第一个窗口应该包含正常数据 - // 滚动窗口:窗口大小2秒,每200ms一条数据,理论上应该有10条数据 - // 但由于窗口对齐和 watermark 机制,实际数据量可能略有不同 + // The first window should contain normal data + // Scrolling window: window size is 2 seconds, one data entry every 200ms, theoretically there should be 10 data entries + // However, due to window alignment and watermark mechanisms, the actual data volume may vary slightly assert.GreaterOrEqual(t, cnt, 3.0, "第一个窗口应该包含足够的数据(滚动窗口特性)") assert.Equal(t, 0.0, minTemp, "第一个窗口的最小值应该是0(正常数据)") assert.GreaterOrEqual(t, maxTemp, 0.0, "第一个窗口的最大值应该大于等于0") } } - // 验证延迟数据是否被处理 - // 如果延迟数据被正确处理,应该能在后续窗口或更新中看到 - t.Logf("总共触发了 %d 个窗口", windowResultsLen) + // Verify whether delayed data is being processed + // If delay data is handled correctly, it should be visible in future windows or updates + t.Logf("A total of %d windows were triggered", windowResultsLen) } -// TestSQLTumblingWindow_EventTimeWindowAlignment 测试事件时间窗口对齐到epoch +// TestSQLTumblingWindow_EventTimeWindowAlignment Align the event time window to epoch func TestSQLTumblingWindow_EventTimeWindowAlignment(t *testing.T) { t.Parallel() ssql := streamsql.New() @@ -767,12 +767,12 @@ func TestSQLTumblingWindow_EventTimeWindowAlignment(t *testing.T) { } }) - // 使用事件时间:发送数据,验证窗口对齐到epoch - // 窗口大小2秒,应该对齐到2秒的倍数 + // Using event time: send data and align the validation window to epoch + // The window size should be 2 seconds, aligned to multiples of 2 seconds baseTime := time.Now().UnixMilli() - // 发送数据,事件时间从baseTime开始,每200ms一条 - // 第一个窗口应该对齐到小于等于baseTime的最大2秒倍数 + // When sending data, the event time starts from baseTime, with one message every 200ms + // The first window should be aligned to a maximum 2-second multiple of baseTime for i := 0; i < 15; i++ { eventTime := baseTime + int64(i*200) ssql.Emit(map[string]any{ @@ -783,7 +783,7 @@ func TestSQLTumblingWindow_EventTimeWindowAlignment(t *testing.T) { time.Sleep(50 * time.Millisecond) } - // 等待窗口触发 + // Wait for the window to trigger time.Sleep(3 * time.Second) ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) @@ -795,16 +795,16 @@ func TestSQLTumblingWindow_EventTimeWindowAlignment(t *testing.T) { select { case result, ok := <-ch: if !ok { - // channel 已关闭 + // The channel has been closed goto END } - _ = result // 使用结果 + _ = result // Results of use iteration++ case <-time.After(500 * time.Millisecond): - // 500ms 没有新结果,退出 + // Exit after 500 ms without a new result goto END case <-ctx.Done(): - // 超时退出 + // Exiting after the time limit goto END } } @@ -818,8 +818,8 @@ END: require.Greater(t, windowResultsLen, 0, "应该至少触发一个窗口") - // 验证窗口对齐 - windowSizeMs := int64(2000) // 2秒 = 2000毫秒 + // Verify window alignment + windowSizeMs := int64(2000) // 2 seconds = 2000 milliseconds for i, window := range windowResultsCopy { if len(window) > 0 { row := window[0] @@ -842,12 +842,12 @@ END: "窗口 %d 的开始时间应该等于前一个窗口的结束时间,prevEndMs=%d, startMs=%d", i+1, prevEndMs, startMs) } - t.Logf("窗口 %d: start=%d, end=%d, size=%dms", i+1, startMs, endMs, endMs-startMs) + t.Logf("Window %d: start=%d, end=%d, size=%dms", i+1, startMs, endMs, endMs-startMs) } } } -// TestSQLTumblingWindow_WatermarkTriggerTiming 测试Watermark触发窗口的时机 +// TestSQLTumblingWindow_WatermarkTriggerTiming Test the timing of the Watermark trigger window func TestSQLTumblingWindow_WatermarkTriggerTiming(t *testing.T) { t.Parallel() ssql := streamsql.New() @@ -877,21 +877,21 @@ func TestSQLTumblingWindow_WatermarkTriggerTiming(t *testing.T) { } }) - // 使用事件时间:发送数据,验证watermark触发时机 - baseTime := time.Now().UnixMilli() - 10000 // 使用10秒前作为基准 + // Use event time: Send data to verify the timing of watermark trigger + baseTime := time.Now().UnixMilli() - 10000 // Use 10 seconds before as the baseline maxOutOfOrdernessMs := int64(500) // 500ms - // 第一阶段:发送数据到第一个窗口 [alignedStart, alignedStart+2000) - // 计算对齐后的窗口开始时间 + // Stage One: Send data to the first window [alignedStart, alignedStart+2000) + // Calculate the window start time after alignment windowSizeMs := int64(2000) alignedStart := (baseTime / windowSizeMs) * windowSizeMs firstWindowEnd := alignedStart + windowSizeMs - t.Logf("第一个窗口: [%d, %d)", alignedStart, firstWindowEnd) + t.Logf("First window: [%d, %d)", alignedStart, firstWindowEnd) - // 发送数据,事件时间在第一个窗口内 + // Send data, event time within the first window for i := 0; i < 10; i++ { - eventTime := alignedStart + int64(i*200) // 在窗口内 + eventTime := alignedStart + int64(i*200) // Inside the window ssql.Emit(map[string]any{ "deviceId": "sensor001", "eventTime": eventTime, @@ -900,28 +900,28 @@ func TestSQLTumblingWindow_WatermarkTriggerTiming(t *testing.T) { time.Sleep(50 * time.Millisecond) } - // 发送一个事件时间刚好等于window_end的数据,推进watermark + // Send data with an event time exactly equal to window_end to advance the watermark // watermark = maxEventTime - maxOutOfOrderness = firstWindowEnd - 500 - // 此时 watermark < firstWindowEnd,窗口不应该触发 + // At this point, watermark < firstWindowEnd, and the window should not be triggered ssql.Emit(map[string]any{ "deviceId": "sensor001", "eventTime": firstWindowEnd, "temperature": 100.0, }) - // 等待watermark更新(watermark更新间隔200ms) + // Wait for watermark updates (watermark update interval 200ms) time.Sleep(500 * time.Millisecond) - // 发送一个事件时间超过window_end的数据,推进watermark + // Send data that has an event time longer than window_end and advance the watermark // watermark = maxEventTime - maxOutOfOrderness = (firstWindowEnd + 1000) - 500 = firstWindowEnd + 500 - // 此时 watermark >= firstWindowEnd,窗口应该触发 + // At this point, watermark > = firstWindowEnd, the window should be triggered ssql.Emit(map[string]any{ "deviceId": "sensor001", "eventTime": firstWindowEnd + 1000, "temperature": 200.0, }) - // 等待窗口触发 + // Wait for the window to trigger time.Sleep(1 * time.Second) ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) @@ -933,16 +933,16 @@ func TestSQLTumblingWindow_WatermarkTriggerTiming(t *testing.T) { select { case result, ok := <-ch: if !ok { - // channel 已关闭 + // The channel has been closed goto END } - _ = result // 使用结果 + _ = result // Results of use iteration++ case <-time.After(500 * time.Millisecond): - // 500ms 没有新结果,退出 + // Exit after 500 ms without a new result goto END case <-ctx.Done(): - // 超时退出 + // Exiting after the time limit goto END } } @@ -956,7 +956,7 @@ END: require.Greater(t, windowResultsLen, 0, "应该至少触发一个窗口") - // 验证第一个窗口的触发时机 + // Verify the timing of the first window trigger if windowResultsLen > 0 { firstWindow := windowResultsCopy[0] if len(firstWindow) > 0 { @@ -972,17 +972,17 @@ END: assert.Equal(t, firstWindowEnd, endMs, "第一个窗口的结束时间应该正确,expected=%d, actual=%d", firstWindowEnd, endMs) - // 验证窗口在watermark >= window_end时触发 - // 由于watermark = maxEventTime - maxOutOfOrderness - // 当maxEventTime = firstWindowEnd + 1000时,watermark = firstWindowEnd + 500 - // watermark >= firstWindowEnd,窗口应该触发 - t.Logf("✓ 窗口在watermark >= window_end时正确触发") - t.Logf("窗口: [%d, %d), 触发时maxEventTime >= %d", start, end, end+maxOutOfOrdernessMs) + // The validation window is triggered when watermark >= window_end + // Since watermark = maxEventTime - maxOutOfOrderness + // When maxEventTime = firstWindowEnd + 1000, watermark = firstWindowEnd + 500 + // watermark > = firstWindowEnd, the window should be triggered + t.Logf("✓ The window is correctly triggered when watermark > = window_end") + t.Logf("Window: [%d, %d), triggered maxEventTime > = %d", start, end, end+maxOutOfOrdernessMs) } } } -// TestSQLTumblingWindow_AllowedLatenessUpdate 测试AllowedLateness的延迟更新 +// TestSQLTumblingWindow_AllowedLatenessUpdate Test latency updates for AllowedLateness func TestSQLTumblingWindow_AllowedLatenessUpdate(t *testing.T) { t.Parallel() ssql := streamsql.New() @@ -1018,10 +1018,10 @@ func TestSQLTumblingWindow_AllowedLatenessUpdate(t *testing.T) { windowSizeMs := int64(2000) alignedStart := (baseTime / windowSizeMs) * windowSizeMs firstWindowEnd := alignedStart + windowSizeMs - allowedLatenessMs := int64(1000) // 1秒 + allowedLatenessMs := int64(1000) // 1 second - // 第一阶段:发送正常数据,触发第一个窗口 - t.Log("第一阶段:发送正常数据,触发第一个窗口") + // Stage One: Send normal data and trigger the first window + t.Log("Stage One: Send normal data and trigger the first window") for i := 0; i < 10; i++ { eventTime := alignedStart + int64(i*200) ssql.Emit(map[string]any{ @@ -1032,17 +1032,17 @@ func TestSQLTumblingWindow_AllowedLatenessUpdate(t *testing.T) { time.Sleep(50 * time.Millisecond) } - // 推进watermark,触发第一个窗口 + // Push the watermark to trigger the first window ssql.Emit(map[string]any{ "deviceId": "sensor001", "eventTime": firstWindowEnd + 1000, "temperature": 100.0, }) - // 等待第一个窗口触发 + // Wait for the first window to trigger time.Sleep(1 * time.Second) - // 收集第一个窗口的结果 + // Collect the results from the first window firstWindowReceived := false firstWindowCnt := 0.0 firstWindowMax := 0.0 @@ -1055,27 +1055,27 @@ func TestSQLTumblingWindow_AllowedLatenessUpdate(t *testing.T) { select { case res, ok := <-ch: if !ok { - // channel 已关闭 - t.Fatal("应该收到第一个窗口") + // The channel has been closed + t.Fatal("The first window should be received") } if len(res) > 0 { firstWindowReceived = true firstWindowCnt = res[0]["cnt"].(float64) firstWindowMax = res[0]["max_temp"].(float64) - t.Logf("第一个窗口(初始): cnt=%.0f, max=%.0f", firstWindowCnt, firstWindowMax) + t.Logf("First window (initial): cnt = %.0f, max = %.0f", firstWindowCnt, firstWindowMax) } iteration++ case <-time.After(500 * time.Millisecond): - // 500ms 没有新结果 + // No new results for 500ms iteration++ case <-ctx.Done(): - t.Fatal("应该收到第一个窗口") + t.Fatal("The first window should be received") } } - // 第二阶段:发送延迟数据(事件时间在第一个窗口内,但在AllowedLateness范围内) - t.Log("第二阶段:发送延迟数据(事件时间在第一个窗口内)") - lateDataTimes := []int64{300, 600, 900} // 延迟数据的事件时间(相对于alignedStart) + // Stage 2: Send delayed data (event time within the first window but within the AllowedLateness range) + t.Log("Stage Two: Sending Delayed Data (Event Time in the First Window)") + lateDataTimes := []int64{300, 600, 900} // Event time of delayed data (relative to alignedStart) lateDataTemps := []float64{30.0, 31.0, 32.0} for i, lateTime := range lateDataTimes { eventTime := alignedStart + lateTime @@ -1087,17 +1087,17 @@ func TestSQLTumblingWindow_AllowedLatenessUpdate(t *testing.T) { time.Sleep(100 * time.Millisecond) } - // 继续发送正常数据,推进watermark(但不超过window_end + allowedLateness) + // Continue sending normal data, advancing the watermark (but not exceeding window_end + allowedLateness) ssql.Emit(map[string]any{ "deviceId": "sensor001", - "eventTime": firstWindowEnd + allowedLatenessMs - 100, // 在allowedLateness范围内 + "eventTime": firstWindowEnd + allowedLatenessMs - 100, // Within the range of allowedLateness "temperature": 200.0, }) - // 等待延迟更新 + // Waiting for delayed updates time.Sleep(1 * time.Second) - // 收集所有窗口结果 + // Collect all window results ctx2, cancel2 := context.WithTimeout(context.Background(), 5*time.Second) defer cancel2() maxIterations2 := 20 @@ -1107,16 +1107,16 @@ func TestSQLTumblingWindow_AllowedLatenessUpdate(t *testing.T) { select { case result, ok := <-ch: if !ok { - // channel 已关闭 + // The channel has been closed goto END } - _ = result // 使用结果 + _ = result // Results of use iteration2++ case <-time.After(500 * time.Millisecond): - // 500ms 没有新结果,退出 + // Exit after 500 ms without a new result goto END case <-ctx2.Done(): - // 超时退出 + // Exiting after the time limit goto END } } @@ -1130,7 +1130,7 @@ END: require.Greater(t, windowResultsLen, 0, "应该至少触发一个窗口") - // 验证延迟更新 + // Verification delay updates hasLateUpdate := false for i, window := range windowResultsCopy { if len(window) > 0 { @@ -1146,28 +1146,28 @@ END: if startMs == alignedStart && endMs == firstWindowEnd { if cnt > firstWindowCnt { hasLateUpdate = true - t.Logf("✓ 窗口延迟更新: cnt从%.0f增加到%.0f, max从%.0f增加到%.0f", + t.Logf("✓ Window Delay Updates: cnt increased from %.0f to %.0f, max from %.0f to %.0f", firstWindowCnt, cnt, firstWindowMax, maxTemp) - // 验证延迟数据被包含 + // Validation latency data is included assert.GreaterOrEqual(t, maxTemp, 30.0, "延迟更新应该包含延迟数据,maxTemp应该>=30.0,实际: %.0f", maxTemp) } } - t.Logf("窗口 %d: [%d, %d), cnt=%.0f, max=%.0f", i+1, start, end, cnt, maxTemp) + t.Logf("Window %d: [%d, %d), cnt=%.0f, max=%.0f", i+1, start, end, cnt, maxTemp) } } if !hasLateUpdate { - t.Logf("⚠ 提示:未检测到延迟更新,可能延迟数据未被处理或窗口已关闭") + t.Logf("⚠ Prompt: No delayed updates detected; the delayed data may not have been processed or the window may have been closed") } else { - t.Logf("✓ AllowedLateness功能正常工作,延迟数据触发窗口更新") + t.Logf("✓ AllowedLateness functions work normally, delayed data triggers window updates") } } -// TestSQLTumblingWindow_IdleSourceMechanism 测试Idle Source机制 -// 验证当数据源空闲时,watermark基于处理时间推进,窗口能够正常关闭 +// TestSQLTumblingWindow_IdleSourceMechanism Test the Idle Source mechanism +// Verification When the data source is idle, the watermark advances processing time and the window closes normally func TestSQLTumblingWindow_IdleSourceMechanism(t *testing.T) { t.Parallel() ssql := streamsql.New() @@ -1197,18 +1197,18 @@ func TestSQLTumblingWindow_IdleSourceMechanism(t *testing.T) { } }) - // 使用事件时间:发送数据,然后停止发送,验证窗口能够关闭 + // Using event time: send data, then stop sending and the validation window can close baseTime := time.Now().UnixMilli() - 10000 - windowSizeMs := int64(2000) // 2秒 + windowSizeMs := int64(2000) // 2 seconds - // 计算对齐后的第一个窗口开始时间 + // Calculate the start time of the first window after alignment alignedStart := (baseTime / windowSizeMs) * windowSizeMs firstWindowEnd := alignedStart + windowSizeMs - t.Logf("第一个窗口: [%d, %d)", alignedStart, firstWindowEnd) + t.Logf("First window: [%d, %d)", alignedStart, firstWindowEnd) - // 第一阶段:发送数据,创建窗口 - t.Log("第一阶段:发送数据,创建窗口") + // Stage One: Send data and create a window + t.Log("Stage One: Send data and create a window") for i := 0; i < 5; i++ { eventTime := alignedStart + int64(i*200) ssql.Emit(map[string]any{ @@ -1219,12 +1219,12 @@ func TestSQLTumblingWindow_IdleSourceMechanism(t *testing.T) { time.Sleep(50 * time.Millisecond) } - // 第二阶段:停止发送数据,等待Idle Source机制触发 - // IdleTimeout = 2秒,意味着2秒无数据后,watermark会基于处理时间推进 - t.Log("第二阶段:停止发送数据,等待Idle Source机制触发(IdleTimeout=2s)") - time.Sleep(3 * time.Second) // 等待超过IdleTimeout,确保watermark推进 + // Stage Two: Stop sending data and wait for the Idle Source mechanism to trigger + // IdleTimeout = 2 seconds, meaning after 2 seconds without data, the watermark will proceed based on processing time + t.Log("Stage Two: Stop sending data and wait for the Idle Source mechanism to trigger (IdleTimeout=2s)") + time.Sleep(3 * time.Second) // Wait beyond IdleTimeout to ensure watermark progress - // 收集窗口结果 + // Collect window results ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() maxIterations := 20 @@ -1234,16 +1234,16 @@ func TestSQLTumblingWindow_IdleSourceMechanism(t *testing.T) { select { case result, ok := <-ch: if !ok { - // channel 已关闭 + // The channel has been closed goto END } - _ = result // 使用结果 + _ = result // Results of use iteration++ case <-time.After(500 * time.Millisecond): - // 500ms 没有新结果,退出 + // Exit after 500 ms without a new result goto END case <-ctx.Done(): - // 超时退出 + // Exiting after the time limit goto END } } @@ -1255,10 +1255,10 @@ END: copy(windowResultsCopy, windowResults) windowResultsMu.Unlock() - // 验证窗口能够关闭(即使没有新数据) + // The validation window can be closed (even if no new data is found). require.Greater(t, windowResultsLen, 0, "应该至少触发一个窗口(即使数据源空闲)") - // 验证窗口数据 + // Validate window data if windowResultsLen > 0 { firstWindow := windowResultsCopy[0] if len(firstWindow) > 0 { @@ -1267,8 +1267,8 @@ END: end := row["end"].(int64) cnt := row["cnt"].(float64) - // 验证窗口边界正确 - // window_start() 和 window_end() 返回纳秒,需要转换为毫秒 + // Verify that window boundaries are correct + // window_start() and window_end() return nanoseconds and need to be converted to milliseconds startMs := start / int64(time.Millisecond) endMs := end / int64(time.Millisecond) assert.Equal(t, alignedStart, startMs, @@ -1276,17 +1276,17 @@ END: assert.Equal(t, firstWindowEnd, endMs, "第一个窗口的结束时间应该正确,expected=%d, actual=%d", firstWindowEnd, endMs) - // 验证窗口包含数据 + // The validation window contains data assert.Greater(t, cnt, 0.0, "窗口应该包含数据") - t.Logf("✓ Idle Source机制正常工作,窗口在数据源空闲时能够关闭") - t.Logf("窗口: [%d, %d), cnt=%.0f", start, end, cnt) + t.Logf("✓ Idle Source mechanism works properly; windows can close when data sources are idle") + t.Logf("Window: [%d, %d), cnt=%.0f", start, end, cnt) } } } -// TestSQLTumblingWindow_IdleSourceDisabled 测试Idle Source机制未启用的情况 -// 验证当IdleTimeout=0(禁用)时,如果数据源空闲,窗口无法关闭 +// TestSQLTumblingWindow_IdleSourceDisabled Testing if the Idle Source mechanism is not enabled +// Verify that when IdleTimeout=0 (disabled), if the data source is idle, the window cannot be closed func TestSQLTumblingWindow_IdleSourceDisabled(t *testing.T) { t.Parallel() ssql := streamsql.New() @@ -1321,8 +1321,8 @@ func TestSQLTumblingWindow_IdleSourceDisabled(t *testing.T) { windowSizeMs := int64(2000) alignedStart := (baseTime / windowSizeMs) * windowSizeMs - // 发送数据,但事件时间不足以触发窗口 - t.Log("发送数据,但事件时间不足以触发窗口") + // Data is sent, but the event time is insufficient to trigger the window + t.Log("Data is sent, but the event time is insufficient to trigger the window") for i := 0; i < 3; i++ { eventTime := alignedStart + int64(i*200) ssql.Emit(map[string]any{ @@ -1333,12 +1333,12 @@ func TestSQLTumblingWindow_IdleSourceDisabled(t *testing.T) { time.Sleep(50 * time.Millisecond) } - // 停止发送数据,等待一段时间 - // 由于IdleTimeout未启用,watermark不会基于处理时间推进 - t.Log("停止发送数据,等待(IdleTimeout未启用)") + // Stop sending data and wait for a while + // Since IdleTimeout is not enabled, the watermark does not advance based on processing time + t.Log("Stop sending data and wait (IdleTimeout not enabled)") time.Sleep(3 * time.Second) - // 收集窗口结果 + // Collect window results ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() maxIterations := 20 @@ -1348,16 +1348,16 @@ func TestSQLTumblingWindow_IdleSourceDisabled(t *testing.T) { select { case result, ok := <-ch: if !ok { - // channel 已关闭 + // The channel has been closed goto END } - _ = result // 使用结果 + _ = result // Results of use iteration++ case <-time.After(500 * time.Millisecond): - // 500ms 没有新结果,退出 + // Exit after 500 ms without a new result goto END case <-ctx.Done(): - // 超时退出 + // Exiting after the time limit goto END } } @@ -1367,8 +1367,8 @@ END: windowResultsLen := len(windowResults) windowResultsMu.Unlock() - // 注意:这个测试可能无法完全验证窗口无法关闭 - // 因为如果watermark已经推进到足够的位置,窗口可能已经触发 - // 这个测试主要用于对比:启用Idle Source vs 未启用Idle Source - t.Logf("窗口结果数量: %d(IdleTimeout未启用)", windowResultsLen) + // Note: This test may not fully verify that the window cannot be closed + // Because if the watermark has advanced enough to a sufficient position, the window may have already been triggered + // This test is mainly used for comparison: enabling Idle Source versus not enabling Idle Source + t.Logf("Number of window results: %d (IdleTimeout not enabled)", windowResultsLen) } diff --git a/types/analytic.go b/types/analytic.go index 18bc76c..537a8aa 100644 --- a/types/analytic.go +++ b/types/analytic.go @@ -2,13 +2,13 @@ package types import "strconv" -// AnalyticSelfToken 是"表达式包分析函数"回代模板里的占位(如 ts - __analytic_self__): -// 求值期把分析函数结果写入行的该键,再求整个外层表达式。 +// AnalyticSelfToken is a placeholder in the "expression package analysis function" regeneration template (e.g., ts - __analytic_self__): +// During the evaluation period, the analysis function results are written into the key of the row, and the entire outer expression is then calculated. const AnalyticSelfToken = "__analytic_self__" -// AnalyticSelfTokenN 返回表达式内第 i 个分析调用在回代模板里的占位标识符。 -// i==0 沿用 AnalyticSelfToken(单调用向后兼容),i>0 用 __analytic_self___。 -// 同一表达式含多个分析调用时(如 acc_max(v) - acc_min(v)),每个调用各分配一个占位。 +// AnalyticSelfTokenN Returns the placeholder identifier used in the parse template for the i-th analysis call in the expression within the expression. +// i==0 uses AnalyticSelfToken (single calls are backward compatible), i>0 uses __analytic_self___. +// When the same expression contains multiple parse calls (e.g., acc_max(v) - acc_min(v)), each call is assigned a placeholder. func AnalyticSelfTokenN(i int) string { if i == 0 { return AnalyticSelfToken @@ -16,52 +16,52 @@ func AnalyticSelfTokenN(i int) string { return "__analytic_self_" + strconv.Itoa(i) + "__" } -// AnalyticCall 描述"表达式包分析函数"里的单个分析调用(一个字段可含多个,如 acc_max(v) - acc_min(v))。 +// AnalyticCall describes a single parsing call in the "Expression Package Analysis Function" (a field can contain multiple entries, e.g., acc_max(v) - acc_min(v)). type AnalyticCall struct { - FuncName string // 函数名,如 "acc_max" - BareCall string // 完整调用文本(不含 OVER),如 "acc_max(v)" - Args []string // 原始参数表达式片段(未求值),如 ["v"] + FuncName string // Function name, such as "acc_max" + BareCall string // Full call text (excluding OVER), such as "acc_max(v)" + Args []string // Fragments of original parameter expressions (not evaluated), such as ["v"] } -// OverSpec 描述分析函数的 OVER 子句。 -// 仅支持 PARTITION BY 和 WHEN,不支持 ORDER BY / ROWS frame(那是 Flink 模型)。 +// OverSpec describes the OVER clause of the analysis function. +// It only supports PARTITION BY and WHEN, not ORDER BY / ROWS frames (which is the Flink model). type OverSpec struct { - PartitionBy []string // 分区字段,状态按分区独立维护 - When string // WHEN 条件表达式;满足才更新状态,否则复用旧值 + PartitionBy []string // Partitioned fields maintain their status independently by partition + When string // WHEN conditional expression; The status is updated only when the condition is satisfied; otherwise, the old value is reused } -// AnalyticField 描述 SELECT 中的分析函数字段(带可选 OVER)。 -// 走直连路径(EmitSync),由流级状态机逐条求值,不进聚合路径。 +// AnalyticField describes the number segment of the analysis function in SELECT (with optional OVER). +// Uses a direct connection path (EmitSync), evaluating each item by the stream-level state machine without entering the aggregation path. type AnalyticField struct { - FuncName string // 函数名,如 "lag" - Args []string // 原始参数表达式片段(未求值),如 ["temp","1"] - Expression string // 完整调用文本(不含 OVER),如 "lag(temp, 1)" - Alias string // 输出列名(多列函数仅作内部句柄,实际列名由结果决定) - Over *OverSpec // OVER 子句,nil 表示无 - // MultiColumn 标记多列动态输出函数(changed_cols):其求值结果为 map[colname]value, - // 投影时按 prefix+colname 扇出为多个输出列。仅 SELECT。 + FuncName string // Function name, such as "lag" + Args []string // Original parameter expression fragments (not evaluated), such as ["temp","1"] + Expression string // Full call text (excluding OVER), such as "lag(temp, 1)" + Alias string // Output column names (multi-column functions only serve as internal handles; actual column names are determined by the results) + Over *OverSpec // In the OVER clause, nil means none + // MultiColumn Flags the multi-column dynamic output function (changed_cols): its evaluation result is map[colname]value, + // During projection, fan out multiple output columns by pressing prefix+colname. Only SELECT. MultiColumn bool - // WrapperExpr 外层算术/表达式回代模板:当字段是"表达式包分析函数"(如 ts - lag(ts)) - // 时,分析调用子串被替换为占位 __analytic_self__,得 "ts - __analytic_self__"。 - // 求值期先算出分析函数值,代入占位再求整个表达式。空表示纯分析字段。 + // WrapperExpr Outer Arithmetic/Expression Regeneration Template: When the field is an "expression package analysis function" (e.g., ts - lag(ts)) + // the analysis call substring is replaced with a placeholder __analytic_self__, resulting in "ts - __analytic_self__". + // During the evaluation period, first calculate the analysis function value, substitute it into the placeholder, and then compute the entire expression. Empty indicates a pure analysis field. WrapperExpr string - // Calls 表达式内全部 Analytic 调用(按出现顺序)。纯单调用字段长度为 1; - // 多调用(acc_max(v) - acc_min(v))长度 ≥ 2,每个调用在 WrapperExpr 里对应 - // types.AnalyticSelfTokenN(i) 占位。FuncName/Args/Expression 仍保留首个调用值供旧路径。 + // All Analytic calls within the Calls expression (in order of occurrence). Pure single call field length is 1; + // Multiple calls (acc_max(v) - acc_min(v)) length ≥ 2, each call corresponds to a WrapperExpr + // types.AnalyticSelfTokenN(i) occupies a position. FuncName/Args/Expression still retains the first call value for the old path. Calls []AnalyticCall - // InlineAggDisplay 窗口查询里分析函数参数内联聚合的重写映射:隐藏键→显示名。 - // 如 changed_cols("t",true,avg(temperature)) 在窗口查询里,avg(temperature) 被提取为 - // 隐藏计算字段 __winagg_0__,这里记录 {"__winagg_0__":"avg"},输出列名取显示名(→ tavg)。 + // In the InlineAggDisplay window query, analyze the override mapping of function parameter inline aggregation: hide key → display name. + // For example, changed_cols("t", true, avg(temperature)) in window queries is extracted as + // Hide the calculation field __winagg_0__, record {"__winagg_0__":"avg"}, and set the output column name to display name (→ tavg). InlineAggDisplay map[string]string } -// WhereAnalyticCall 描述 WHERE 条件中出现的分析函数调用。 -// 解析期从 WHERE 文本提取,分配占位符;求值期在 WHERE 之前算出值, -// 注入 dataMap[Placeholder],WHERE 文本中的原始调用已被替换为占位符。 +// WhereAnalyticCall describes the analysis function call that appears in the WHERE condition. +// During the parsing phase, extract from the WHERE text and assign placeholders; The value is calculated before WHERE during the calculation period, +// Injecting dataMap[Placeholder], the original call in the WHERE text is replaced with placeholders. type WhereAnalyticCall struct { - Placeholder string // 合成键,如 "__analytic_0__" - FuncName string // 函数名 - Args []string // 原始参数片段 - Expression string // 完整调用文本(不含 OVER) - Over *OverSpec // OVER 子句,nil 表示无 + Placeholder string // Composite bonds, such as "__analytic_0__" + FuncName string // Function name + Args []string // Original parameter fragments + Expression string // Full call text (excluding OVER) + Over *OverSpec // In the OVER clause, nil means none } diff --git a/types/config.go b/types/config.go index 2a1e33a..6a44d85 100644 --- a/types/config.go +++ b/types/config.go @@ -21,29 +21,29 @@ const ( // Config stream processing configuration type Config struct { // SQL processing related configuration - WindowConfig WindowConfig `json:"windowConfig"` - GroupFields []string `json:"groupFields"` - SelectFields map[string]aggregator.AggregateType `json:"selectFields"` - FieldAlias map[string]string `json:"fieldAlias"` + WindowConfig WindowConfig `json:"windowConfig"` + GroupFields []string `json:"groupFields"` + SelectFields map[string]aggregator.AggregateType `json:"selectFields"` + FieldAlias map[string]string `json:"fieldAlias"` // SelectAlias maps a SELECT item's raw expression to its AS alias (e.g. // "m.location" -> "loc"). The aggregation path uses it to name output // columns for grouped non-aggregate columns, matching the direct path // (where the alias is applied during SimpleField compilation). - SelectAlias map[string]string `json:"selectAlias"` - SimpleFields []string `json:"simpleFields"` - FieldExpressions map[string]FieldExpression `json:"fieldExpressions"` - PostAggExpressions []PostAggregationExpression `json:"postAggExpressions"` // Post-aggregation expressions - FieldOrder []string `json:"fieldOrder"` // Original order of fields in SELECT statement - Where string `json:"where"` - Having string `json:"having"` + SelectAlias map[string]string `json:"selectAlias"` + SimpleFields []string `json:"simpleFields"` + FieldExpressions map[string]FieldExpression `json:"fieldExpressions"` + PostAggExpressions []PostAggregationExpression `json:"postAggExpressions"` // Post-aggregation expressions + FieldOrder []string `json:"fieldOrder"` // Original order of fields in SELECT statement + Where string `json:"where"` + Having string `json:"having"` // Feature switches NeedWindow bool `json:"needWindow"` Distinct bool `json:"distinct"` - // Mode 选择执行路径(直连/窗口/CEP)。NeedWindow 保留为兼容谓词(==ExecWindow)。 + // Mode selects the execution path (direct connection/window/CEP). NeedWindow is reserved as a compatibility predicate (==ExecWindow). Mode ExecMode `json:"mode"` - // MatchRecognize 携带 MATCH_RECOGNIZE 子句;非空时 Mode=ExecCEP。 + // MatchRecognize carries MATCH_RECOGNIZE clauses; Non-null Mode=ExecCEP. MatchRecognize *MatchRecognizeSpec `json:"matchRecognize,omitempty"` // Result control @@ -60,15 +60,15 @@ type Config struct { // When set, stream fields can be qualified as "s." in SELECT/WHERE. SourceAlias string `json:"sourceAlias"` - // AnalyticFields 分析函数字段(带可选 OVER)。走直连路径,由 - // 流级状态机逐条求值,不进聚合路径。空表示无分析函数。 + // Numeric segment of the AnalyticFields analysis function (with optional OVER). Take the direct connection path, by + // The flow level state machine evaluates each item without entering the aggregation path. An empty means no analysis function. AnalyticFields []AnalyticField `json:"analyticFields"` - // WhereAnalyticCalls WHERE 中出现的分析函数调用;解析期从 WHERE 文本提取并 - // 替换为占位符,求值期在 WHERE 之前算出值注入 dataMap[Placeholder]。 + // Analysis function calls that appear in WhereAnalyticCalls WHERE; During the parsing period, extract and extract from the WHERE text + // Replace with placeholders, and the evaluation period is calculated before WHERE to inject the value into dataMap[Placeholder]. WhereAnalyticCalls []WhereAnalyticCall `json:"whereAnalyticCalls"` - // AnalyticMaxPartitions 每个分析函数字段 PARTITION 状态的分区数上限,超出按 - // LRU 淘汰最久未用的分区。≤0 表示用默认值(见 stream.defaultMaxPartitions)。 - // 由 WithAnalyticMaxPartitions 注入。高基数分区键(如设备上万)且内存充裕时可调高。 + // AnalyticMaxPartitions is the maximum number of partitions in each segment of the analysis function's PARTITION state; if exceeded, click + // LRU eliminates the longest-unused partitions. ≤0 means the default value is used (see stream.defaultMaxPartitions). + // Injected by WithAnalyticMaxPartitions. High base zone keys (such as tens of thousands of devices on the same scale) can be adjusted higher when memory is abundant. AnalyticMaxPartitions int `json:"analyticMaxPartitions"` // Logger is the per-instance logger for the stream pipeline. Injected by @@ -143,7 +143,7 @@ type WindowConfig struct { // group without buffering raw rows. Populated from the parsed SELECT for // windowType=global only. TriggerCondition string `json:"triggerCondition,omitempty"` - SelectFields map[string]aggregator.AggregateType `json:"selectFields,omitempty"` + SelectFields map[string]aggregator.AggregateType `json:"selectFields,omitempty"` FieldAlias map[string]string `json:"fieldAlias,omitempty"` } @@ -156,19 +156,19 @@ type FieldExpression struct { // PostAggregationExpression represents an expression that needs to be evaluated after aggregation type PostAggregationExpression struct { - OutputField string `json:"outputField"` // 输出字段名 - OriginalExpr string `json:"originalExpr"` // 原始表达式 - ExpressionTemplate string `json:"expressionTemplate"` // 表达式模板 - RequiredFields []AggregationFieldInfo `json:"requiredFields"` // 依赖的聚合字段 + OutputField string `json:"outputField"` // Output field name + OriginalExpr string `json:"originalExpr"` // Primitive expression + ExpressionTemplate string `json:"expressionTemplate"` // Expression template + RequiredFields []AggregationFieldInfo `json:"requiredFields"` // Dependent aggregated fields } // AggregationFieldInfo holds information about an aggregation function in an expression type AggregationFieldInfo struct { - FuncName string `json:"funcName"` // 函数名,如 "first_value" - InputField string `json:"inputField"` // 输入字段,如 "displayNum" - Placeholder string `json:"placeholder"` // 占位符,如 "__first_value_0__" - AggType aggregator.AggregateType `json:"aggType"` // 聚合类型 - FullCall string `json:"fullCall"` // 完整函数调用,如 "NTH_VALUE(value, 2)" + FuncName string `json:"funcName"` // Function name, such as "first_value" + InputField string `json:"inputField"` // Input fields, such as "displayNum" + Placeholder string `json:"placeholder"` // Placeholders, such as "__first_value_0__" + AggType aggregator.AggregateType `json:"aggType"` // Types of aggregation + FullCall string `json:"fullCall"` // Full function calls, such as "NTH_VALUE(value, 2)" } // ProjectionSourceType projection source type diff --git a/types/config_test.go b/types/config_test.go index 04127ff..bacff52 100644 --- a/types/config_test.go +++ b/types/config_test.go @@ -24,7 +24,7 @@ import ( "github.com/stretchr/testify/assert" ) -// TestConfig 测试 Config 结构体的基本功能 +// TestConfig tests the basic functionality of the Config structure func TestConfig(t *testing.T) { config := &Config{ WindowConfig: WindowConfig{ @@ -73,7 +73,7 @@ func TestConfig(t *testing.T) { }, } - // 验证基本字段 + // Validate the basic field if config.WindowConfig.Type != "tumbling" { t.Errorf("Expected window type 'tumbling', got '%s'", config.WindowConfig.Type) } @@ -123,7 +123,7 @@ func TestConfig(t *testing.T) { } } -// TestWindowConfig 测试 WindowConfig 结构体 +// TestWindowConfig tests the WindowConfig structure func TestWindowConfig(t *testing.T) { windowConfig := WindowConfig{ Type: "sliding", @@ -164,7 +164,7 @@ func TestWindowConfig(t *testing.T) { } } -// TestFieldExpression 测试 FieldExpression 结构体 +// TestFieldExpression tests the FieldExpression structure func TestFieldExpression(t *testing.T) { fieldExpr := FieldExpression{ Field: "total_amount", @@ -192,7 +192,7 @@ func TestFieldExpression(t *testing.T) { } } -// TestProjection 测试 Projection 结构体 +// TestProjection Test the Projection structure func TestProjection(t *testing.T) { projection := Projection{ SourceType: SourceGroupKey, @@ -213,7 +213,7 @@ func TestProjection(t *testing.T) { } } -// TestPerformanceConfig 测试 PerformanceConfig 结构体 +// TestPerformanceConfig Tests the PerformanceConfig structure func TestPerformanceConfig(t *testing.T) { perfConfig := PerformanceConfig{ BufferConfig: BufferConfig{ @@ -251,11 +251,11 @@ func TestPerformanceConfig(t *testing.T) { } } -// TestConfigDefaults 测试 Config 结构体的默认值 +// TestConfigDefaults tests the default value of the Config structure func TestConfigDefaults(t *testing.T) { config := &Config{} - // 验证默认值 + // Verify the default value if config.NeedWindow { t.Error("Expected NeedWindow default to be false") } @@ -281,11 +281,11 @@ func TestConfigDefaults(t *testing.T) { } } -// TestNewConfig 测试NewConfig函数 +// TestNewConfig tests the NewConfig function func TestNewConfig(t *testing.T) { config := NewConfig() - // 验证默认值 + // Verify the default value assert.False(t, config.NeedWindow) assert.False(t, config.Distinct) assert.Equal(t, 0, config.Limit) @@ -300,7 +300,7 @@ func TestNewConfig(t *testing.T) { assert.Empty(t, config.Projections) } -// TestNewConfigWithPerformance 测试NewConfigWithPerformance函数 +// TestNewConfigWithPerformance Tests the NewConfigWithPerformance function func TestNewConfigWithPerformance(t *testing.T) { perfConfig := PerformanceConfig{ BufferConfig: BufferConfig{ @@ -314,21 +314,21 @@ func TestNewConfigWithPerformance(t *testing.T) { config := NewConfigWithPerformance(perfConfig) - // 验证性能配置已设置 + // Verify that performance configurations are set assert.Equal(t, 200, config.PerformanceConfig.BufferConfig.DataChannelSize) assert.Equal(t, 150, config.PerformanceConfig.BufferConfig.ResultChannelSize) assert.True(t, config.PerformanceConfig.MonitoringConfig.EnableMonitoring) - // 验证其他字段为默认值 + // Validate other fields as default values assert.False(t, config.NeedWindow) assert.False(t, config.Distinct) } -// TestDefaultPerformanceConfig 测试DefaultPerformanceConfig函数 +// TestDefaultPerformanceConfig tests the DefaultPerformanceConfig function func TestDefaultPerformanceConfig(t *testing.T) { config := DefaultPerformanceConfig() - // 验证缓冲区配置 + // Verify buffer configuration assert.Equal(t, 1000, config.BufferConfig.DataChannelSize) assert.Equal(t, 100, config.BufferConfig.ResultChannelSize) assert.Equal(t, 50, config.BufferConfig.WindowOutputSize) @@ -336,27 +336,27 @@ func TestDefaultPerformanceConfig(t *testing.T) { assert.Equal(t, 10000, config.BufferConfig.MaxBufferSize) assert.Equal(t, 0.8, config.BufferConfig.UsageThreshold) - // 验证溢出配置 + // Verify the overflow configuration assert.Equal(t, "drop", config.OverflowConfig.Strategy) assert.Equal(t, 5*time.Second, config.OverflowConfig.BlockTimeout) assert.True(t, config.OverflowConfig.AllowDataLoss) - // 验证工作器配置 + // Verify worker configuration assert.Equal(t, 4, config.WorkerConfig.SinkPoolSize) assert.Equal(t, 2, config.WorkerConfig.SinkWorkerCount) assert.Equal(t, 10, config.WorkerConfig.MaxRetryRoutines) - // 验证监控配置 + // Verify monitoring configuration assert.False(t, config.MonitoringConfig.EnableMonitoring) assert.Equal(t, 30*time.Second, config.MonitoringConfig.StatsUpdateInterval) assert.False(t, config.MonitoringConfig.EnableDetailedStats) } -// TestHighPerformanceConfig 测试HighPerformanceConfig函数 +// TestHighPerformanceConfig Tests the HighPerformanceConfig function func TestHighPerformanceConfig(t *testing.T) { config := HighPerformanceConfig() - // 验证高性能配置 + // Verify high-performance configurations assert.Equal(t, 5000, config.BufferConfig.DataChannelSize) assert.Equal(t, 500, config.BufferConfig.ResultChannelSize) assert.Equal(t, 200, config.BufferConfig.WindowOutputSize) @@ -366,11 +366,11 @@ func TestHighPerformanceConfig(t *testing.T) { assert.True(t, config.MonitoringConfig.EnableMonitoring) } -// TestLowLatencyConfig 测试LowLatencyConfig函数 +// TestLowLatencyConfig Tests the LowLatencyConfig function func TestLowLatencyConfig(t *testing.T) { config := LowLatencyConfig() - // 验证低延迟配置 + // Verify low-latency configurations assert.Equal(t, 100, config.BufferConfig.DataChannelSize) assert.Equal(t, 50, config.BufferConfig.ResultChannelSize) assert.Equal(t, 20, config.BufferConfig.WindowOutputSize) @@ -380,7 +380,7 @@ func TestLowLatencyConfig(t *testing.T) { assert.Equal(t, 1*time.Second, config.MonitoringConfig.StatsUpdateInterval) } -// TestBufferConfig 测试BufferConfig结构体 +// TestBufferConfig Tests the BufferConfig structure func TestBufferConfig(t *testing.T) { config := BufferConfig{ DataChannelSize: 1000, @@ -399,7 +399,7 @@ func TestBufferConfig(t *testing.T) { assert.Equal(t, 0.75, config.UsageThreshold) } -// TestWorkerConfig 测试WorkerConfig结构体 +// TestWorkerConfig tests the WorkerConfig structure func TestWorkerConfig(t *testing.T) { config := WorkerConfig{ SinkPoolSize: 8, @@ -412,7 +412,7 @@ func TestWorkerConfig(t *testing.T) { assert.Equal(t, 20, config.MaxRetryRoutines) } -// TestMonitoringConfig 测试MonitoringConfig结构体 +// TestMonitoringConfig Tests the MonitoringConfig structure func TestMonitoringConfig(t *testing.T) { warningThresholds := WarningThresholds{ DropRateWarning: 0.01, @@ -437,14 +437,14 @@ func TestMonitoringConfig(t *testing.T) { assert.Equal(t, 0.95, config.WarningThresholds.BufferUsageCritical) } -// TestProjectionSourceType 测试ProjectionSourceType枚举 +// TestProjectionSourceType TestProjectionSourceType enumeration func TestProjectionSourceType(t *testing.T) { assert.Equal(t, ProjectionSourceType(0), SourceGroupKey) assert.Equal(t, ProjectionSourceType(1), SourceAggregateResult) assert.Equal(t, ProjectionSourceType(2), SourceWindowProperty) } -// TestComplexConfig 测试复杂配置组合 +// TestComplexConfig Tests complex configuration combinations func TestComplexConfig(t *testing.T) { config := Config{ WindowConfig: WindowConfig{ @@ -506,7 +506,7 @@ func TestComplexConfig(t *testing.T) { PerformanceConfig: HighPerformanceConfig(), } - // 验证复杂配置 + // Verify complex configurations assert.Equal(t, "sliding", config.WindowConfig.Type) assert.Len(t, config.GroupFields, 3) assert.Len(t, config.SelectFields, 5) @@ -519,7 +519,7 @@ func TestComplexConfig(t *testing.T) { assert.Equal(t, 5000, config.Limit) assert.Len(t, config.Projections, 3) - // 验证字段表达式 + // Validate the field expression discountedExpr := config.FieldExpressions["discounted_total"] assert.Equal(t, "total_amount", discountedExpr.Field) assert.Equal(t, "total_amount * 0.9", discountedExpr.Expression) @@ -530,7 +530,7 @@ func TestComplexConfig(t *testing.T) { assert.Equal(t, "total_amount / order_count", pricePerItemExpr.Expression) assert.Equal(t, []string{"total_amount", "order_count"}, pricePerItemExpr.Fields) - // 验证投影配置 + // Verify the projection configuration assert.Equal(t, SourceGroupKey, config.Projections[0].SourceType) assert.Equal(t, SourceAggregateResult, config.Projections[1].SourceType) assert.Equal(t, SourceWindowProperty, config.Projections[2].SourceType) diff --git a/types/match_recognize.go b/types/match_recognize.go index cd2d688..0209d90 100644 --- a/types/match_recognize.go +++ b/types/match_recognize.go @@ -2,53 +2,53 @@ package types import "time" -// ExecMode 选择一条查询的执行路径。扩展自原 NeedWindow 二分:直连 / 窗口 / CEP。 -// NeedWindow 保留为 ExecMode==ExecWindow 的便捷谓词(向后兼容)。 +// ExecMode selects the execution path for a query. Extended from the original NeedWindow binary split: direct connection / window / CEP. +// NeedWindow is reserved as ExecMode==ExecWindow's convenient predicate (backward compatible). type ExecMode int const ( - // ExecDirect 纯直连路径:逐事件 EmitSync/projectDirectRow,含分析函数状态机。 + // ExecDirect pure direct connection path: event-by-event EmitSync/projectDirectRow, including analysis function state machine. ExecDirect ExecMode = iota - // ExecWindow 窗口/聚合路径:Emit 异步入窗,触发时聚合输出。 + // ExecWindow window/aggregation path: Emit asynchronously enters the window, and aggregates output when triggered. ExecWindow - // ExecCEP MATCH_RECOGNIZE 模式识别路径:逐事件推进 NFA,匹配完成时输出。 + // ExecCEP MATCH_RECOGNIZE Pattern Recognition Path: Advances NFA event-by-event and outputs when matching is complete. ExecCEP ) -// RowsPerMatch 选择每次匹配的输出形态。 +// RowsPerMatch selects the output form for each match. type RowsPerMatch int const ( - // RowsPerMatchOne 每次匹配输出一行(MEASURES 在匹配末行求值)。默认。 + // RowsPerMatchOne outputs one line per match (MEASURES evaluates the last line of the match). Default. RowsPerMatchOne RowsPerMatch = iota - // RowsPerMatchAll 每次匹配输出全部行(含 RUNNING/FINAL 逐行求值)。 + // RowsPerMatchAll outputs all rows (including RUNNING/FINAL line by line evaluation) for each match. RowsPerMatchAll ) -// AfterMatchSkip 选择匹配完成后下一轮匹配的起点(SQL 标准 AFTER MATCH SKIP)。 +// AfterMatchSkip selects the starting point for the next round of matching after the match is completed (SQL standard AFTER MATCH SKIP). type AfterMatchSkip int const ( - // SkipPastLastRow 从匹配末行的下一行开始(默认)。 + // SkipPastLastRow starts from the next row after matching the last line (default). SkipPastLastRow AfterMatchSkip = iota - // SkipToNextRow 从匹配首行的下一行开始(允许重叠)。 + // SkipToNextRow starts from the next row that matches the first row (overlapping allowed). SkipToNextRow - // SkipToFirst 跳到指定符号在匹配中的首行位置。 + // SkipToFirst jumps to the position of the specified symbol in the first row of the match. SkipToFirst - // SkipToLast 跳到指定符号在匹配中的末行位置。 + // SkipToLast jumps to the last row position of the specified symbol in the match. SkipToLast - // SkipToVariable 跳到指定符号(等同 TO LAST 的别名形式)。 + // SkipToVariable jumps to the specifier (equivalent to the alias form for TO LAST). SkipToVariable ) -// Quantifier 描述模式原子的量词边界。Max<0 表示无上界(* / + / {n,})。 +// Quantifier describes the quantifier boundary of a model atom. Max<0 represents the Supreme Realm (* / + / {n,}). type Quantifier struct { Min int Max int - Greedy bool // true=贪婪(默认),false=懒惰(量词后跟 ?) + Greedy bool // true = greed (default), false = laziness (measure word followed by?) } -// PatternKind 标识组合式模式节点的类型。 +// PatternKind identifies the type of composable mode node. type PatternKind int const ( @@ -61,47 +61,47 @@ const ( PatternExclusion ) -// PatternNode 是模式树的一个节点。组合式:Sequence/Alternation/Group/Permute 用 -// Children 组合;Repetition 用 Children[0] 携带单个被重复子式 + Quant;Literal 用 Symbol。 +// PatternNode is a node in the pattern tree. Combinatorial: used for Sequence/Alternation/Group/Permute +// Children group; Repetition carries a single repeatedn subexpression + Quant with Children[0]; Literal uses Symbol. type PatternNode struct { Kind PatternKind - Symbol string // Literal:模式变量名 - Children []*PatternNode // Sequence/Alternation/Group/Permute/Repetition 的子节点 - Quant *Quantifier // Repetition 的量词 + Symbol string // Literal: The name of the pattern variable + Children []*PatternNode // Child nodes of Sequence/Alternation/Group/Permute/Repetition + Quant *Quantifier // Repetition } -// Measure 描述 MEASURES 子句的一项: AS 。 +// Measure describes one of the MEASURES clauses: AS. type Measure struct { Expr string Alias string } -// MatchDefine 描述 DEFINE 子句的一项: AS 。 -// 未出现在 DEFINE 中的模式变量恒为真(SQL 标准)。 +// MatchDefine describes a DEFINE clause that is: AS. +// Pattern variables not appearing in DEFINE are always true (SQL standard). type MatchDefine struct { Symbol string Cond string } -// MatchSubset 描述 SUBSET 子句: = ( [, ...])。 +// MatchSubset describes the SUBSET clause: = ( [,...]). type MatchSubset struct { Name string Symbols []string } -// MatchRecognizeSpec 持有 MATCH_RECOGNIZE 子句的全部子结构。 +// MatchRecognizeSpec holds the entire substructure of the MATCH_RECOGNIZE clause. type MatchRecognizeSpec struct { PartitionBy []string OrderBy []OrderByField Measures []Measure RowsPerMatch RowsPerMatch Skip AfterMatchSkip - SkipSymbol string // SKIP TO FIRST/LAST/ 的目标符号 + SkipSymbol string // The target symbol for SKIP TO FIRST/LAST/ Pattern *PatternNode Subsets []MatchSubset - Within time.Duration // 0 表示用默认上限(CEP 强制有界) + Within time.Duration // 0 indicates the default limit (CEP is mandatory bounded) Defines []MatchDefine } -// 默认 WITHIN 上限:未显式指定 WITHIN 时强制施加,保证边缘内存有界。 +// Default WITHIN limit: Enforced when WITHIN is not explicitly specified, ensuring bounded edge memory. const DefaultMatchWithin = 1 * time.Hour diff --git a/types/row_test.go b/types/row_test.go index 9138b17..79aa18b 100644 --- a/types/row_test.go +++ b/types/row_test.go @@ -21,7 +21,7 @@ import ( "time" ) -// TestRow 测试 Row 结构体的基本功能 +// TestRow tests the basic functions of the Row structure func TestRow(t *testing.T) { testTime := time.Date(2024, 1, 1, 12, 0, 0, 0, time.UTC) testData := map[string]any{ @@ -40,17 +40,17 @@ func TestRow(t *testing.T) { Slot: testSlot, } - // 测试 GetTimestamp 方法 + // Test the GetTimestamp method if !row.GetTimestamp().Equal(testTime) { t.Errorf("Expected timestamp %v, got %v", testTime, row.GetTimestamp()) } - // 测试 Timestamp 字段 + // Test the Timestamp field if !row.Timestamp.Equal(testTime) { t.Errorf("Expected timestamp %v, got %v", testTime, row.Timestamp) } - // 测试 Data 字段 + // Test the Data field if row.Data == nil { t.Error("Expected Data to be non-nil") } @@ -72,7 +72,7 @@ func TestRow(t *testing.T) { t.Errorf("Expected status 'active', got %v", dataMap["status"]) } - // 测试 Slot 字段 + // Test the Slot field if row.Slot == nil { t.Error("Expected Slot to be non-nil") } @@ -86,7 +86,7 @@ func TestRow(t *testing.T) { } } -// TestRowWithNilData 测试 Row 结构体处理 nil 数据的情况 +// TestRowWithNilData tests how the Row structure handles nil data func TestRowWithNilData(t *testing.T) { testTime := time.Date(2024, 1, 1, 12, 0, 0, 0, time.UTC) @@ -96,27 +96,27 @@ func TestRowWithNilData(t *testing.T) { Slot: nil, } - // 测试 GetTimestamp 方法仍然正常工作 + // Testing the GetTimestamp method still works fine if !row.GetTimestamp().Equal(testTime) { t.Errorf("Expected timestamp %v, got %v", testTime, row.GetTimestamp()) } - // 测试 nil 数据 + // Test NIL data if row.Data != nil { t.Error("Expected Data to be nil") } - // 测试 nil slot + // Test Nil Slot if row.Slot != nil { t.Error("Expected Slot to be nil") } } -// TestRowWithDifferentDataTypes 测试 Row 结构体处理不同数据类型的情况 +// TestRowWithDifferentDataTypes The test row structure handles different data types func TestRowWithDifferentDataTypes(t *testing.T) { testTime := time.Date(2024, 1, 1, 12, 0, 0, 0, time.UTC) - // 测试字符串数据 + // Test string data rowString := &Row{ Timestamp: testTime, Data: "test string data", @@ -126,7 +126,7 @@ func TestRowWithDifferentDataTypes(t *testing.T) { t.Errorf("Expected string data 'test string data', got %v", rowString.Data) } - // 测试数字数据 + // Test the digital data rowNumber := &Row{ Timestamp: testTime, Data: 42, @@ -136,7 +136,7 @@ func TestRowWithDifferentDataTypes(t *testing.T) { t.Errorf("Expected number data 42, got %v", rowNumber.Data) } - // 测试布尔数据 + // Test Boolean data rowBool := &Row{ Timestamp: testTime, Data: true, @@ -146,7 +146,7 @@ func TestRowWithDifferentDataTypes(t *testing.T) { t.Errorf("Expected boolean data true, got %v", rowBool.Data) } - // 测试切片数据 + // Test slicing data sliceData := []string{"item1", "item2", "item3"} rowSlice := &Row{ Timestamp: testTime, @@ -167,7 +167,7 @@ func TestRowWithDifferentDataTypes(t *testing.T) { } } -// TestRowEventInterface 测试 Row 实现 RowEvent 接口 +// TestRowEventInterface Implements the RowEvent interface for testing rows func TestRowEventInterface(t *testing.T) { testTime := time.Date(2024, 1, 1, 12, 0, 0, 0, time.UTC) @@ -176,7 +176,7 @@ func TestRowEventInterface(t *testing.T) { Data: "test data", } - // 验证 Row 实现了 RowEvent 接口 + // Verify that the row implements the RowEvent interface var rowEvent RowEvent = row if !rowEvent.GetTimestamp().Equal(testTime) { @@ -184,7 +184,7 @@ func TestRowEventInterface(t *testing.T) { } } -// TestRowZeroTime 测试 Row 结构体处理零时间的情况 +// TestRowZeroTime Tests how the Row structure handles zero time func TestRowZeroTime(t *testing.T) { zeroTime := time.Time{} @@ -202,7 +202,7 @@ func TestRowZeroTime(t *testing.T) { } } -// TestRowConcurrentAccess 测试 Row 结构体的并发访问 +// TestRowConcurrentAccess Concurrent access to the test row structure func TestRowConcurrentAccess(t *testing.T) { testTime := time.Date(2024, 1, 1, 12, 0, 0, 0, time.UTC) @@ -211,7 +211,7 @@ func TestRowConcurrentAccess(t *testing.T) { Data: "test data", } - // 启动多个 goroutine 并发访问 GetTimestamp 方法 + // Launch multiple goroutines to concurrently access the GetTimestamp method done := make(chan bool, 10) for i := 0; i < 10; i++ { go func() { @@ -225,7 +225,7 @@ func TestRowConcurrentAccess(t *testing.T) { }() } - // 等待所有 goroutine 完成 + // Wait for all goroutines to complete for i := 0; i < 10; i++ { <-done } diff --git a/types/timeslot_test.go b/types/timeslot_test.go index da90cce..31ad77b 100644 --- a/types/timeslot_test.go +++ b/types/timeslot_test.go @@ -21,7 +21,7 @@ import ( "time" ) -// TestNewTimeSlot 测试 NewTimeSlot 构造函数 +// TestNewTimeSlot Tests the NewTimeSlot constructor func TestNewTimeSlot(t *testing.T) { start := time.Date(2024, 1, 1, 12, 0, 0, 0, time.UTC) end := time.Date(2024, 1, 1, 13, 0, 0, 0, time.UTC) @@ -49,11 +49,11 @@ func TestNewTimeSlot(t *testing.T) { } } -// TestNewTimeSlotWithNil 测试 NewTimeSlot 处理 nil 参数的情况 +// TestNewTimeSlotWithNil tests how NewTimeSlot handles nil parameters func TestNewTimeSlotWithNil(t *testing.T) { start := time.Date(2024, 1, 1, 12, 0, 0, 0, time.UTC) - // 测试 end 为 nil + // Test end is nil ts1 := NewTimeSlot(&start, nil) if ts1.Start == nil { t.Error("Expected Start to be non-nil") @@ -62,7 +62,7 @@ func TestNewTimeSlotWithNil(t *testing.T) { t.Error("Expected End to be nil") } - // 测试 start 为 nil + // Test start is nil ts2 := NewTimeSlot(nil, &start) if ts2.Start != nil { t.Error("Expected Start to be nil") @@ -71,7 +71,7 @@ func TestNewTimeSlotWithNil(t *testing.T) { t.Error("Expected End to be non-nil") } - // 测试两者都为 nil + // Both tests are nil ts3 := NewTimeSlot(nil, nil) if ts3.Start != nil { t.Error("Expected Start to be nil") @@ -81,7 +81,7 @@ func TestNewTimeSlotWithNil(t *testing.T) { } } -// TestTimeSlotHash 测试 Hash 方法 +// TestTimeSlotHash Testing the hash method func TestTimeSlotHash(t *testing.T) { start := time.Date(2024, 1, 1, 12, 0, 0, 0, time.UTC) end := time.Date(2024, 1, 1, 13, 0, 0, 0, time.UTC) @@ -92,12 +92,12 @@ func TestTimeSlotHash(t *testing.T) { hash1 := ts1.Hash() hash2 := ts2.Hash() - // 相同的时间槽应该产生相同的哈希值 + // The same time slots should produce the same hash value if hash1 != hash2 { t.Errorf("Expected same hash for identical time slots, got %d and %d", hash1, hash2) } - // 不同的时间槽应该产生不同的哈希值 + // Different time slots should produce different hash values differentEnd := time.Date(2024, 1, 1, 14, 0, 0, 0, time.UTC) ts3 := NewTimeSlot(&start, &differentEnd) hash3 := ts3.Hash() @@ -107,13 +107,13 @@ func TestTimeSlotHash(t *testing.T) { } } -// TestTimeSlotContains 测试 Contains 方法 +// TestTimeSlotContains Test Contains method func TestTimeSlotContains(t *testing.T) { start := time.Date(2024, 1, 1, 12, 0, 0, 0, time.UTC) end := time.Date(2024, 1, 1, 13, 0, 0, 0, time.UTC) ts := NewTimeSlot(&start, &end) - // 测试边界情况 + // Test boundary conditions if !ts.Contains(start) { t.Error("Expected Contains to return true for start time") } @@ -122,13 +122,13 @@ func TestTimeSlotContains(t *testing.T) { t.Error("Expected Contains to return false for end time (exclusive)") } - // 测试范围内的时间 + // Time within the test range midTime := time.Date(2024, 1, 1, 12, 30, 0, 0, time.UTC) if !ts.Contains(midTime) { t.Error("Expected Contains to return true for time within range") } - // 测试范围外的时间 + // Testing time beyond the scope of the test beforeStart := time.Date(2024, 1, 1, 11, 59, 59, 0, time.UTC) if ts.Contains(beforeStart) { t.Error("Expected Contains to return false for time before start") @@ -140,7 +140,7 @@ func TestTimeSlotContains(t *testing.T) { } } -// TestGetStartTime 测试 GetStartTime 方法 +// TestGetStartTime Tests the GetStartTime method func TestGetStartTime(t *testing.T) { start := time.Date(2024, 1, 1, 12, 0, 0, 0, time.UTC) end := time.Date(2024, 1, 1, 13, 0, 0, 0, time.UTC) @@ -155,14 +155,14 @@ func TestGetStartTime(t *testing.T) { t.Errorf("Expected start time %v, got %v", start, *startTime) } - // 测试 nil TimeSlot + // Test nil TimeSlot var nilTS *TimeSlot nilStartTime := nilTS.GetStartTime() if nilStartTime != nil { t.Error("Expected GetStartTime to return nil for nil TimeSlot") } - // 测试 Start 为 nil 的情况 + // Test the case where Start is nil tsWithNilStart := NewTimeSlot(nil, &end) nilStart := tsWithNilStart.GetStartTime() if nilStart != nil { @@ -170,7 +170,7 @@ func TestGetStartTime(t *testing.T) { } } -// TestGetEndTime 测试 GetEndTime 方法 +// TestGetEndTime Tests the GetEndTime method func TestGetEndTime(t *testing.T) { start := time.Date(2024, 1, 1, 12, 0, 0, 0, time.UTC) end := time.Date(2024, 1, 1, 13, 0, 0, 0, time.UTC) @@ -185,14 +185,14 @@ func TestGetEndTime(t *testing.T) { t.Errorf("Expected end time %v, got %v", end, *endTime) } - // 测试 nil TimeSlot + // Test nil TimeSlot var nilTS *TimeSlot nilEndTime := nilTS.GetEndTime() if nilEndTime != nil { t.Error("Expected GetEndTime to return nil for nil TimeSlot") } - // 测试 End 为 nil 的情况 + // Test the case where End is nil tsWithNilEnd := NewTimeSlot(&start, nil) nilEnd := tsWithNilEnd.GetEndTime() if nilEnd != nil { @@ -200,7 +200,7 @@ func TestGetEndTime(t *testing.T) { } } -// TestWindowStart 测试 WindowStart 方法 +// TestWindowStart tests the WindowStart method func TestWindowStart(t *testing.T) { start := time.Date(2024, 1, 1, 12, 0, 0, 0, time.UTC) end := time.Date(2024, 1, 1, 13, 0, 0, 0, time.UTC) @@ -213,14 +213,14 @@ func TestWindowStart(t *testing.T) { t.Errorf("Expected window start %d, got %d", expectedStart, windowStart) } - // 测试 nil TimeSlot + // Test nil TimeSlot var nilTS *TimeSlot nilWindowStart := nilTS.WindowStart() if nilWindowStart != 0 { t.Errorf("Expected WindowStart to return 0 for nil TimeSlot, got %d", nilWindowStart) } - // 测试 Start 为 nil 的情况 + // Test the case where Start is nil tsWithNilStart := NewTimeSlot(nil, &end) nilStart := tsWithNilStart.WindowStart() if nilStart != 0 { @@ -228,7 +228,7 @@ func TestWindowStart(t *testing.T) { } } -// TestWindowEnd 测试 WindowEnd 方法 +// TestWindowEnd tests the WindowEnd method func TestWindowEnd(t *testing.T) { start := time.Date(2024, 1, 1, 12, 0, 0, 0, time.UTC) end := time.Date(2024, 1, 1, 13, 0, 0, 0, time.UTC) @@ -241,14 +241,14 @@ func TestWindowEnd(t *testing.T) { t.Errorf("Expected window end %d, got %d", expectedEnd, windowEnd) } - // 测试 nil TimeSlot + // Test nil TimeSlot var nilTS *TimeSlot nilWindowEnd := nilTS.WindowEnd() if nilWindowEnd != 0 { t.Errorf("Expected WindowEnd to return 0 for nil TimeSlot, got %d", nilWindowEnd) } - // 测试 End 为 nil 的情况 + // Test the case where End is nil tsWithNilEnd := NewTimeSlot(&start, nil) nilEnd := tsWithNilEnd.WindowEnd() if nilEnd != 0 { @@ -256,9 +256,9 @@ func TestWindowEnd(t *testing.T) { } } -// TestTimeSlotEdgeCases 测试 TimeSlot 的边界情况 +// TestTimeSlotEdgeCases Tests the boundaries of the TimeSlot func TestTimeSlotEdgeCases(t *testing.T) { - // 测试相同的开始和结束时间 + // Test the same start and end times sameTime := time.Date(2024, 1, 1, 12, 0, 0, 0, time.UTC) ts := NewTimeSlot(&sameTime, &sameTime) @@ -266,7 +266,7 @@ func TestTimeSlotEdgeCases(t *testing.T) { t.Error("Expected Contains to return false when start equals end") } - // 测试开始时间晚于结束时间的情况 + // Cases where the test starts later than the end time start := time.Date(2024, 1, 1, 13, 0, 0, 0, time.UTC) end := time.Date(2024, 1, 1, 12, 0, 0, 0, time.UTC) invalidTS := NewTimeSlot(&start, &end) @@ -277,18 +277,18 @@ func TestTimeSlotEdgeCases(t *testing.T) { } } -// TestTimeSlotConcurrentAccess 测试 TimeSlot 的并发访问 +// TestTimeSlotConcurrentAccess Tests for concurrent access to the TimeSlot func TestTimeSlotConcurrentAccess(t *testing.T) { start := time.Date(2024, 1, 1, 12, 0, 0, 0, time.UTC) end := time.Date(2024, 1, 1, 13, 0, 0, 0, time.UTC) ts := NewTimeSlot(&start, &end) - // 启动多个 goroutine 并发访问 TimeSlot 方法 + // Start multiple goroutines to concurrently access TimeSlot methods done := make(chan bool, 10) for i := 0; i < 10; i++ { go func() { for j := 0; j < 100; j++ { - // 测试各种方法的并发访问 + // Testing concurrent access for various methods _ = ts.Hash() _ = ts.Contains(start) _ = ts.GetStartTime() @@ -300,7 +300,7 @@ func TestTimeSlotConcurrentAccess(t *testing.T) { }() } - // 等待所有 goroutine 完成 + // Wait for all goroutines to complete for i := 0; i < 10; i++ { <-done } diff --git a/utils/cast/cast.go b/utils/cast/cast.go index 2790467..2936afa 100644 --- a/utils/cast/cast.go +++ b/utils/cast/cast.go @@ -277,7 +277,7 @@ func ToString(input any) string { return v } -// convertNumericToString 将数字类型转换为字符串 +// convertNumericToString converts the numeric type into a string func convertNumericToString(input any) (string, bool) { switch v := input.(type) { case float64: @@ -309,7 +309,7 @@ func convertNumericToString(input any) (string, bool) { } } -// convertComplexToString 将复杂类型转换为字符串 +// convertComplexToString converts complex types into strings func convertComplexToString(input any) (string, error) { switch v := input.(type) { case []byte: @@ -319,7 +319,7 @@ func convertComplexToString(input any) (string, error) { case error: return v.Error(), nil case map[any]any: - // 转换为 map[string]any + // Convert to map[string]any convertedInput := make(map[string]any) for k, value := range v { convertedInput[fmt.Sprintf("%v", k)] = value @@ -330,7 +330,7 @@ func convertComplexToString(input any) (string, error) { } } -// marshalToString 通过JSON序列化转换为字符串 +// marshalToString converts to strings via JSON serialization func marshalToString(input any) (string, error) { if newValue, err := json.Marshal(input); err == nil { return string(newValue), nil @@ -353,16 +353,16 @@ func ToStringE(input any) (string, error) { case bool: return strconv.FormatBool(v), nil default: - // 尝试数字类型转换 + // Try converting the number type if str, ok := convertNumericToString(input); ok { return str, nil } - // 尝试复杂类型转换 + // Try complex type conversions return convertComplexToString(input) } } -// ConvertIntToTime 将整数时间戳转换为 time.Time +// ConvertIntToTime converts integer timestamps to time.Time func ConvertIntToTime(timestampInt int64, timeUnit time.Duration) time.Time { switch timeUnit { case time.Second: @@ -374,6 +374,6 @@ func ConvertIntToTime(timestampInt int64, timeUnit time.Duration) time.Time { case time.Nanosecond: return time.Unix(0, timestampInt) default: - return time.Unix(timestampInt, 0) // 默认按秒处理 + return time.Unix(timestampInt, 0) // Default processing is done by second } } diff --git a/utils/cast/cast_test.go b/utils/cast/cast_test.go index 9259209..1910ddd 100644 --- a/utils/cast/cast_test.go +++ b/utils/cast/cast_test.go @@ -109,7 +109,7 @@ func TestToBoolENumericTypes(t *testing.T) { } } -// TestConvertIntToTime 测试ConvertIntToTime函数 +// TestConvertIntToTime tests the ConvertIntToTime function func TestConvertIntToTime(t *testing.T) { tests := []struct { name string @@ -121,7 +121,7 @@ func TestConvertIntToTime(t *testing.T) { {"milliseconds", 1609459200000, time.Millisecond, time.Unix(0, 1609459200000*int64(time.Millisecond))}, {"microseconds", 1609459200000000, time.Microsecond, time.Unix(0, 1609459200000000*int64(time.Microsecond))}, {"nanoseconds", 1609459200000000000, time.Nanosecond, time.Unix(0, 1609459200000000000)}, - {"default unit", 1609459200, time.Minute, time.Unix(1609459200, 0)}, // 默认按秒处理 + {"default unit", 1609459200, time.Minute, time.Unix(1609459200, 0)}, // Default processing is done by second } for _, tt := range tests { @@ -134,7 +134,7 @@ func TestConvertIntToTime(t *testing.T) { } } -// testStringer 实现fmt.Stringer接口 +// testStringer implements fmt.Stringer interface type testStringer struct { value string } @@ -143,10 +143,10 @@ func (ts testStringer) String() string { return ts.value } -// TestToStringEComplexTypes 测试ToStringE函数的复杂类型 +// TestToStringEComplexTypes tests complex types of the ToStringE function func TestToStringEComplexTypes(t *testing.T) { - // 测试map[any]any类型 + // Test the map[any]any type mapInterfaceInterface := map[any]any{ "key1": "value1", 123: "value2", @@ -169,7 +169,7 @@ func TestToStringEComplexTypes(t *testing.T) { t.Errorf("ToStringE() error = %v, wantErr %v", err, tt.hasErr) } if !tt.hasErr { - // 对于JSON序列化的结果,由于map的顺序不确定,我们检查是否包含关键内容 + // For the results of JSON serialization, since the map order is uncertain, we check whether it contains key content if tt.name == "map[any]any" { if len(got) == 0 || got[0] != '{' || got[len(got)-1] != '}' { t.Errorf("ToStringE() = %v, expected JSON format", got) diff --git a/utils/fieldpath/fieldpath_error_test.go b/utils/fieldpath/fieldpath_error_test.go index 506d7bf..38eaccd 100644 --- a/utils/fieldpath/fieldpath_error_test.go +++ b/utils/fieldpath/fieldpath_error_test.go @@ -4,7 +4,7 @@ import ( "testing" ) -// TestSetNestedField 测试设置嵌套字段功能 +// TestSetNestedField tests the nested field function func TestSetNestedField(t *testing.T) { tests := []struct { name string @@ -50,7 +50,7 @@ func TestSetNestedField(t *testing.T) { } } -// TestGetNestedFieldBasic 测试基本嵌套字段访问功能 +// TestGetNestedFieldBasic tests basic nested field access functionality func TestGetNestedFieldBasic(t *testing.T) { data := map[string]any{ "user": map[string]any{ @@ -109,7 +109,7 @@ func TestGetNestedFieldBasic(t *testing.T) { } } -// TestIsNestedField 测试嵌套字段检测功能 +// TestIsNestedField Tests nested field detection functionality func TestIsNestedField(t *testing.T) { tests := []struct { name string @@ -148,7 +148,7 @@ func TestIsNestedField(t *testing.T) { } } -// TestFieldPathErrorHandling 测试错误处理 +// TestFieldPathErrorHandling Test error handling func TestFieldPathErrorHandling(t *testing.T) { data := map[string]any{ "valid": "value", @@ -179,14 +179,14 @@ func TestFieldPathErrorHandling(t *testing.T) { testData = data } - // 这些调用不应该panic + // These calls shouldn't panic _, _ = GetNestedField(testData, tt.path) _ = IsNestedField(tt.path) }) } } -// TestExtractTopLevelField 测试提取顶级字段功能 +// TestExtractTopLevelField tests the top-level field extraction function func TestExtractTopLevelField(t *testing.T) { tests := []struct { name string @@ -225,7 +225,7 @@ func TestExtractTopLevelField(t *testing.T) { } } -// TestMapKeyAccess 测试Map键访问功能 +// TestMapKeyAccess Tests the Map key access function func TestMapKeyAccess(t *testing.T) { data := map[string]any{ "stringMap": map[string]any{ @@ -272,7 +272,7 @@ func TestMapKeyAccess(t *testing.T) { } } -// TestNegativeArrayIndex 测试负数组索引功能 +// TestNegativeArrayIndex tests the negative array index function func TestNegativeArrayIndex(t *testing.T) { data := map[string]any{ "items": []any{"first", "second", "third"}, @@ -318,7 +318,7 @@ func TestNegativeArrayIndex(t *testing.T) { } } -// TestFieldAccessErrorMessage 测试字段访问错误消息 +// TestFieldAccessErrorMessage (Test field access error message). func TestFieldAccessErrorMessage(t *testing.T) { err := &FieldAccessError{ Path: "test.path", @@ -331,7 +331,7 @@ func TestFieldAccessErrorMessage(t *testing.T) { } } -// TestParseFieldPathErrors 测试ParseFieldPath的错误处理 +// TestParseFieldPathErrors Test the error handling of ParseFieldPath func TestParseFieldPathErrors(t *testing.T) { tests := []struct { name string @@ -365,7 +365,7 @@ func TestParseFieldPathErrors(t *testing.T) { } } -// TestGetNestedFieldErrorCases 测试GetNestedField的错误情况 +// TestGetNestedFieldErrorCases Tests for GetNestedField errors func TestGetNestedFieldErrorCases(t *testing.T) { tests := []struct { name string @@ -439,7 +439,7 @@ func TestGetNestedFieldErrorCases(t *testing.T) { } } -// TestStructFieldAccess 测试结构体字段访问 +// TestStructFieldAccess tests the field access to the structure field func TestStructFieldAccess(t *testing.T) { type TestStruct struct { Name string @@ -461,13 +461,13 @@ func TestStructFieldAccess(t *testing.T) { t.Errorf("expected: John, got: %v", result) } - // 测试不存在的结构体字段 + // Test the nonexistent structure field result, found = GetNestedField(data, "user.NonExistent") if found { t.Error("expected not to find field") } - // 测试结构体指针 + // Test the structure pointer ptrData := map[string]any{ "user": &TestStruct{ Name: "Jane", @@ -483,7 +483,7 @@ func TestStructFieldAccess(t *testing.T) { } } -// 辅助函数 +// Auxiliary function func createDeepNestedData(depth int) any { if depth <= 0 { return "deep_value" @@ -527,7 +527,7 @@ func createCircularReference() any { func createLargeDataStructure() any { data := make(map[string]any) - for i := 0; i < 100; i++ { // 减少数据量避免测试超时 + for i := 0; i < 100; i++ { // Reduce data volume to avoid test timeouts data["key_"+string(rune('0'+i%10))] = map[string]any{ "nested": map[string]any{ "value": i, @@ -537,7 +537,7 @@ func createLargeDataStructure() any { return data } -// TestSetNestedFieldErrors 测试SetNestedField的错误处理 +// TestSetNestedFieldErrors: Tests the error handling of SetNestedField func TestSetNestedFieldErrors(t *testing.T) { tests := []struct { name string @@ -592,7 +592,7 @@ func TestSetNestedFieldErrors(t *testing.T) { } } -// TestValidateFieldPathExtended 测试ValidateFieldPath函数的扩展情况 +// TestValidateFieldPathExtended tests the extension of the ValidateFieldPath function func TestValidateFieldPathExtended(t *testing.T) { tests := []struct { name string @@ -642,7 +642,7 @@ func TestValidateFieldPathExtended(t *testing.T) { } } -// TestGetFieldPathDepthExtended 测试GetFieldPathDepth函数的扩展情况 +// TestGetFieldPathDepthExtended tests the extension of the GetFieldPathDepth function func TestGetFieldPathDepthExtended(t *testing.T) { tests := []struct { name string @@ -667,12 +667,12 @@ func TestGetFieldPathDepthExtended(t *testing.T) { { name: "无效路径回退测试", path: "field[invalid.path", - expected: 2, // 回退到简单计算: ["field[invalid", "path"] + expected: 2, // Backtrack to simple calculation: ["field[invalid", "path"] }, { name: "空括号内容", path: "field[].nested", - expected: 2, // 解析失败,回退到简单计算 + expected: 2, // Parsing fails, reverting to simple calculations }, } @@ -686,7 +686,7 @@ func TestGetFieldPathDepthExtended(t *testing.T) { } } -// TestNormalizeFieldPathExtended 测试NormalizeFieldPath函数的扩展情况 +// TestNormalizeFieldPathExtended tests the extension of the NormalizeFieldPath function func TestNormalizeFieldPathExtended(t *testing.T) { tests := []struct { name string @@ -735,7 +735,7 @@ func TestNormalizeFieldPathExtended(t *testing.T) { } } -// TestGetAllReferencedFieldsExtended 测试GetAllReferencedFields函数的扩展情况 +// TestGetAllReferencedFieldsExtended tests the extension of the GetAllReferencedFields function func TestGetAllReferencedFieldsExtended(t *testing.T) { tests := []struct { name string @@ -772,13 +772,13 @@ func TestGetAllReferencedFieldsExtended(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { result := GetAllReferencedFields(tt.paths) - // 由于返回的是map的键,顺序可能不同,所以需要检查长度和包含关系 + // Since the returned map keys may be in different order, you need to check the length and inclusion relationships if len(result) != len(tt.expected) { t.Errorf("expected length: %v, got: %v", len(tt.expected), len(result)) return } - // 检查每个期望的字段都在结果中 + // Check that every desired field is included in the results for _, expected := range tt.expected { found := false for _, actual := range result { @@ -795,17 +795,17 @@ func TestGetAllReferencedFieldsExtended(t *testing.T) { } } -// TestMapWithIntKeys 测试整数键的Map访问 +// TestMapWithIntKeys tests the map access to integer keys func TestMapWithIntKeys(t *testing.T) { data := map[string]any{ "intMap": map[any]any{ 1: "value1", 2: "value2", - "3": "value3", // 字符串键 + "3": "value3", // String keys }, } - // 测试通过数组索引访问整数键Map(应该成功,因为会尝试整数键) + // Test to access integer keys via array index (should succeed, since integer keys are attempted) result, found := GetNestedField(data, "intMap[1]") if !found { t.Error("expected to find int key when accessing with index") @@ -814,7 +814,7 @@ func TestMapWithIntKeys(t *testing.T) { t.Errorf("expected: value1, got: %v", result) } - // 测试访问字符串键 + // Test access string keys result, found = GetNestedField(data, "intMap[\"3\"]") if !found { t.Error("expected to find string key in map") @@ -823,14 +823,14 @@ func TestMapWithIntKeys(t *testing.T) { t.Errorf("expected: value3, got: %v", result) } - // 测试不存在的键 + // Test keys that don't exist result, found = GetNestedField(data, "intMap[\"999\"]") if found { t.Error("expected not to find non-existent key") } } -// TestArrayAsMapAccess 测试数组作为Map访问的情况 +// TestArrayAsMapAccess Tests the array as a Map access func TestArrayAsMapAccess(t *testing.T) { data := map[string]any{ "mixedMap": map[any]any{ @@ -840,7 +840,7 @@ func TestArrayAsMapAccess(t *testing.T) { }, } - // 测试数字键访问 + // Test the numeric keys result, found := GetNestedField(data, "mixedMap[0]") if !found { t.Error("expected to find numeric key 0") @@ -849,7 +849,7 @@ func TestArrayAsMapAccess(t *testing.T) { t.Errorf("expected: zero, got: %v", result) } - // 测试字符串形式的数字键 + // Test the number key in string form result, found = GetNestedField(data, "mixedMap['1']") if !found { t.Error("expected to find string key '1'") @@ -885,16 +885,16 @@ func TestTypedMapMismatchedKey(t *testing.T) { } } -// TestComplexErrorScenarios 测试复杂的错误场景 +// TestComplexErrorScenarios: Tests complex error scenarios func TestComplexErrorScenarios(t *testing.T) { - // 测试解析失败时的回退机制 + // Testing the rollback mechanism when parsing fails data := map[string]any{ "simple": map[string]any{ "field": "value", }, } - // 这个路径会导致解析失败,但应该回退到简单访问 + // This path will cause parsing failures, but it should be reverted to simple access result, found := GetNestedField(data, "simple.field") if !found { t.Error("expected fallback to simple access to work") @@ -903,14 +903,14 @@ func TestComplexErrorScenarios(t *testing.T) { t.Errorf("expected: value, got: %v", result) } - // 测试SetNestedField的解析失败回退 + // Test the parsing failure of SetNestedField and rollback testData := make(map[string]any) err := SetNestedField(testData, "simple.field", "test") if err != nil { t.Errorf("unexpected error in fallback: %v", err) } - // 验证值被正确设置 + // Verification values are correctly set if testData["simple"].(map[string]any)["field"] != "test" { t.Error("fallback setting failed") } diff --git a/utils/fieldpath/fieldpath_test.go b/utils/fieldpath/fieldpath_test.go index 5792ae8..3c9272c 100644 --- a/utils/fieldpath/fieldpath_test.go +++ b/utils/fieldpath/fieldpath_test.go @@ -119,7 +119,7 @@ func TestParseFieldPath(t *testing.T) { } func TestGetNestedFieldComplex(t *testing.T) { - // 创建复杂的测试数据 + // Creating complex test data testData := map[string]any{ "users": []any{ map[string]any{ @@ -271,7 +271,7 @@ func TestSetNestedFieldComplex(t *testing.T) { assert.True(t, found) assert.Equal(t, "Bob", result) - // 确保原有数据仍然存在 + // Ensure the original data still exists id, found := GetNestedField(data, "user.id") assert.True(t, found) assert.Equal(t, 1, id) @@ -346,10 +346,10 @@ func TestValidateFieldPath(t *testing.T) { {"users[1].name", false}, {"config['database']", false}, {"matrix[0][1]", false}, - {"data[abc]", true}, // 无效括号内容 - {"data[", true}, // 未闭合括号 - {"data]", false}, // 仅右括号不算错误(当作普通字段名) - {"", true}, // 空路径 + {"data[abc]", true}, // Invalid parenthesis content + {"data[", true}, // Unclosed parentheses + {"data]", false}, // Only the right parenthesis is not considered an error (treated as a regular field name) + {"", true}, // Empty path } for _, tt := range tests { @@ -410,7 +410,7 @@ func TestGetFieldPathDepth(t *testing.T) { } } -// TestGetAllReferencedFields 测试GetAllReferencedFields函数 +// TestGetAllReferencedFields tests the GetAllReferencedFields function func TestGetAllReferencedFields(t *testing.T) { tests := []struct { name string @@ -452,13 +452,13 @@ func TestGetAllReferencedFields(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { result := GetAllReferencedFields(tt.fieldPaths) - // 由于返回的是map的keys,顺序可能不同,所以需要排序比较 + // Since the returned keys are maps, the order may differ, so sorting and comparison are necessary assert.ElementsMatch(t, tt.expected, result) }) } } -// TestFieldAccessError 测试FieldAccessError类型 +// TestFieldAccessError tests the type FieldAccessError func TestFieldAccessError(t *testing.T) { err := &FieldAccessError{ Path: "invalid.path[abc]", @@ -469,7 +469,7 @@ func TestFieldAccessError(t *testing.T) { assert.Equal(t, expected, err.Error()) } -// TestSetNestedFieldEdgeCases 测试SetNestedField函数的边缘情况 +// TestSetNestedFieldEdgeCases tests the edge conditions of the SetNestedField function func TestSetNestedFieldEdgeCases(t *testing.T) { tests := []struct { name string @@ -489,7 +489,7 @@ func TestSetNestedFieldEdgeCases(t *testing.T) { name: "无效字段路径", fieldPath: "field[abc]", value: "test", - hasError: false, // 会fallback到简单设置 + hasError: false, // It will fall back to the simple settings }, } @@ -510,7 +510,7 @@ func TestSetNestedFieldEdgeCases(t *testing.T) { } } -// TestGetNestedFieldEdgeCases 测试GetNestedField函数的边缘情况 +// TestGetNestedFieldEdgeCases tests the edge conditions of the GetNestedField function func TestGetNestedFieldEdgeCases(t *testing.T) { tests := []struct { name string diff --git a/utils/reflectutil/reflectutil_test.go b/utils/reflectutil/reflectutil_test.go index bf2c878..fe6b316 100644 --- a/utils/reflectutil/reflectutil_test.go +++ b/utils/reflectutil/reflectutil_test.go @@ -21,7 +21,7 @@ import ( "testing" ) -// TestStruct 用于测试的结构体 +// TestStruct is used for testing structures type TestStruct struct { Name string Age int @@ -30,7 +30,7 @@ type TestStruct struct { Balance float64 } -// TestSafeFieldByName 测试 SafeFieldByName 函数的基本功能 +// TestSafeFieldByName Tests the basic functionality of the SafeFieldByName function func TestSafeFieldByName(t *testing.T) { testObj := TestStruct{ Name: "John Doe", @@ -42,7 +42,7 @@ func TestSafeFieldByName(t *testing.T) { v := reflect.ValueOf(testObj) - // 测试获取存在的字段 + // Test to retrieve the fields that exist nameField, err := SafeFieldByName(v, "Name") if err != nil { t.Errorf("Expected no error for existing field 'Name', got: %v", err) @@ -56,7 +56,7 @@ func TestSafeFieldByName(t *testing.T) { t.Errorf("Expected field value 'John Doe', got: %v", nameField.String()) } - // 测试获取 Age 字段 + // Test to get the Age field ageField, err := SafeFieldByName(v, "Age") if err != nil { t.Errorf("Expected no error for existing field 'Age', got: %v", err) @@ -66,7 +66,7 @@ func TestSafeFieldByName(t *testing.T) { t.Errorf("Expected field value 30, got: %v", ageField.Int()) } - // 测试获取 Active 字段 + // Test to get the Active field activeField, err := SafeFieldByName(v, "Active") if err != nil { t.Errorf("Expected no error for existing field 'Active', got: %v", err) @@ -76,7 +76,7 @@ func TestSafeFieldByName(t *testing.T) { t.Errorf("Expected field value true, got: %v", activeField.Bool()) } - // 测试获取 Balance 字段 + // Test to get the Balance field balanceField, err := SafeFieldByName(v, "Balance") if err != nil { t.Errorf("Expected no error for existing field 'Balance', got: %v", err) @@ -87,12 +87,12 @@ func TestSafeFieldByName(t *testing.T) { } } -// TestSafeFieldByNameNonExistentField 测试获取不存在的字段 +// TestSafeFieldByNameNonExistentField tests to retrieve fields that do not exist func TestSafeFieldByNameNonExistentField(t *testing.T) { testObj := TestStruct{Name: "John Doe"} v := reflect.ValueOf(testObj) - // 测试获取不存在的字段 + // Test to retrieve fields that don't exist _, err := SafeFieldByName(v, "NonExistentField") if err == nil { t.Error("Expected error for non-existent field, got nil") @@ -104,9 +104,9 @@ func TestSafeFieldByNameNonExistentField(t *testing.T) { } } -// TestSafeFieldByNameInvalidValue 测试无效的 reflect.Value +// TestSafeFieldByNameInvalidValue Invalid test reflect.Value func TestSafeFieldByNameInvalidValue(t *testing.T) { - // 创建一个无效的 reflect.Value + // Create an invalid reflect.Value var invalidValue reflect.Value _, err := SafeFieldByName(invalidValue, "Name") @@ -120,9 +120,9 @@ func TestSafeFieldByNameInvalidValue(t *testing.T) { } } -// TestSafeFieldByNameNonStructValue 测试非结构体类型的值 +// TestSafeFieldByNameNonStructValue tests the value of the non-structure type func TestSafeFieldByNameNonStructValue(t *testing.T) { - // 测试字符串类型 + // Test string types stringValue := reflect.ValueOf("test string") _, err := SafeFieldByName(stringValue, "Name") if err == nil { @@ -134,7 +134,7 @@ func TestSafeFieldByNameNonStructValue(t *testing.T) { t.Errorf("Expected error message '%s', got: %v", expectedError, err.Error()) } - // 测试整数类型 + // Test integer types intValue := reflect.ValueOf(42) _, err = SafeFieldByName(intValue, "Name") if err == nil { @@ -146,7 +146,7 @@ func TestSafeFieldByNameNonStructValue(t *testing.T) { t.Errorf("Expected error message '%s', got: %v", expectedError, err.Error()) } - // 测试切片类型 + // Test the type of slice sliceValue := reflect.ValueOf([]string{"a", "b", "c"}) _, err = SafeFieldByName(sliceValue, "Name") if err == nil { @@ -159,7 +159,7 @@ func TestSafeFieldByNameNonStructValue(t *testing.T) { } } -// TestSafeFieldByNameWithPointer 测试指针类型的结构体 +// TestSafeFieldByNameWithPointer to test the structure of pointer type func TestSafeFieldByNameWithPointer(t *testing.T) { testObj := &TestStruct{ Name: "Jane Doe", @@ -167,10 +167,10 @@ func TestSafeFieldByNameWithPointer(t *testing.T) { Active: false, } - // 获取指针指向的值 + // Get the value pointed to by the pointer v := reflect.ValueOf(testObj).Elem() - // 测试获取字段 + // Test to get fields nameField, err := SafeFieldByName(v, "Name") if err != nil { t.Errorf("Expected no error for existing field 'Name', got: %v", err) @@ -190,7 +190,7 @@ func TestSafeFieldByNameWithPointer(t *testing.T) { } } -// TestSafeFieldByNameWithInterface 测试接口类型 +// TestSafeFieldByNameWithInterface Test interface type func TestSafeFieldByNameWithInterface(t *testing.T) { var testInterface any = TestStruct{ Name: "Interface Test", @@ -210,14 +210,14 @@ func TestSafeFieldByNameWithInterface(t *testing.T) { } } -// TestSafeFieldByNameEmptyStruct 测试空结构体 +// TestSafeFieldByNameEmptyStruct Tests the empty structure func TestSafeFieldByNameEmptyStruct(t *testing.T) { type EmptyStruct struct{} emptyObj := EmptyStruct{} v := reflect.ValueOf(emptyObj) - // 尝试获取不存在的字段 + // Try to get fields that don't exist _, err := SafeFieldByName(v, "NonExistentField") if err == nil { t.Error("Expected error for non-existent field in empty struct, got nil") @@ -229,12 +229,12 @@ func TestSafeFieldByNameEmptyStruct(t *testing.T) { } } -// TestSafeFieldByNameCaseSensitive 测试字段名大小写敏感性 +// TestSafeFieldByNameCaseSensitive tests field name case sensitivity func TestSafeFieldByNameCaseSensitive(t *testing.T) { testObj := TestStruct{Name: "Case Test"} v := reflect.ValueOf(testObj) - // 测试正确的大小写 + // Test the correct case case nameField, err := SafeFieldByName(v, "Name") if err != nil { t.Errorf("Expected no error for correct case 'Name', got: %v", err) @@ -244,19 +244,19 @@ func TestSafeFieldByNameCaseSensitive(t *testing.T) { t.Errorf("Expected field value 'Case Test', got: %v", nameField.String()) } - // 测试错误的大小写 - _, err = SafeFieldByName(v, "name") // 小写 + // Test for case errors + _, err = SafeFieldByName(v, "name") // Lowercase if err == nil { t.Error("Expected error for incorrect case 'name', got nil") } - _, err = SafeFieldByName(v, "NAME") // 大写 + _, err = SafeFieldByName(v, "NAME") // Uppercase if err == nil { t.Error("Expected error for incorrect case 'NAME', got nil") } } -// TestSafeFieldByNameConcurrentAccess 测试并发访问 +// TestSafeFieldByNameConcurrentAccess tests for concurrent access func TestSafeFieldByNameConcurrentAccess(t *testing.T) { testObj := TestStruct{ Name: "Concurrent Test", @@ -268,12 +268,12 @@ func TestSafeFieldByNameConcurrentAccess(t *testing.T) { v := reflect.ValueOf(testObj) - // 启动多个 goroutine 并发访问 + // Initiate multiple GoRoutine concurrent accesses done := make(chan bool, 10) for i := 0; i < 10; i++ { go func() { for j := 0; j < 100; j++ { - // 测试获取不同字段 + // Test to obtain different fields nameField, err := SafeFieldByName(v, "Name") if err != nil { t.Errorf("Concurrent access error for Name: %v", err) @@ -298,7 +298,7 @@ func TestSafeFieldByNameConcurrentAccess(t *testing.T) { }() } - // 等待所有 goroutine 完成 + // Wait for all goroutines to complete for i := 0; i < 10; i++ { <-done } diff --git a/utils/table/table_test.go b/utils/table/table_test.go index 96d8d47..0a319f9 100644 --- a/utils/table/table_test.go +++ b/utils/table/table_test.go @@ -22,14 +22,14 @@ import ( "github.com/stretchr/testify/assert" ) -// TestPrintTableFromSlice 测试表格打印功能 +// TestPrintTableFromSlice tests the table printing function func TestPrintTableFromSlice(t *testing.T) { - // 测试空数据 + // Test empty data assert.NotPanics(t, func() { PrintTableFromSlice([]map[string]any{}, nil) }, "空数据不应该panic") - // 测试正常数据 + // Test normal data data := []map[string]any{ {"name": "Alice", "age": 30, "city": "New York"}, {"name": "Bob", "age": 25, "city": "Los Angeles"}, @@ -38,30 +38,30 @@ func TestPrintTableFromSlice(t *testing.T) { PrintTableFromSlice(data, nil) }, "正常数据不应该panic") - // 测试带字段顺序的数据 + // Test data with field order fieldOrder := []string{"name", "city", "age"} assert.NotPanics(t, func() { PrintTableFromSlice(data, fieldOrder) }, "带字段顺序的数据不应该panic") } -// TestPrintTableBorder 测试边框打印功能 +// TestPrintTableBorder tests border printing functionality func TestPrintTableBorder(t *testing.T) { - // 测试正常宽度 + // Test the normal width assert.NotPanics(t, func() { colWidths := []int{5, 8, 6} PrintTableBorder(colWidths) }, "PrintTableBorder不应该panic") - // 测试空宽度 + // Test the empty width assert.NotPanics(t, func() { PrintTableBorder([]int{}) }, "空宽度数组不应该panic") } -// TestFormatTableData 测试数据格式化功能 +// TestFormatTableData test data formatting function func TestFormatTableData(t *testing.T) { - // 测试切片数据 + // Test slicing data sliceData := []map[string]any{ {"device": "sensor1", "temp": 25.5}, } @@ -69,18 +69,18 @@ func TestFormatTableData(t *testing.T) { FormatTableData(sliceData, nil) }, "切片数据不应该panic") - // 测试单个map数据 + // Test individual map data mapData := map[string]any{"device": "sensor1", "temp": 25.5} assert.NotPanics(t, func() { FormatTableData(mapData, nil) }, "map数据不应该panic") - // 测试其他类型数据 + // Testing other types of data assert.NotPanics(t, func() { FormatTableData("string data", nil) }, "字符串数据不应该panic") - // 测试空数据 + // Test empty data assert.NotPanics(t, func() { FormatTableData([]map[string]any{}, nil) }, "空切片数据不应该panic") @@ -90,9 +90,9 @@ func TestFormatTableData(t *testing.T) { }, "空map数据不应该panic") } -// TestPrintTableFromSliceEdgeCases 测试边缘情况 +// TestPrintTableFromSliceEdgeCases tests edge conditions func TestPrintTableFromSliceEdgeCases(t *testing.T) { - // 测试字段顺序包含不存在的字段 + // The test field order includes fields that do not exist data := []map[string]any{ {"a": "1", "b": "2"}, } @@ -101,17 +101,17 @@ func TestPrintTableFromSliceEdgeCases(t *testing.T) { PrintTableFromSlice(data, fieldOrder) }, "字段顺序包含不存在字段不应该panic") - // 测试数据行中某些字段缺失 + // Some fields in the test data row are missing dataWithMissingFields := []map[string]any{ {"name": "Alice", "age": 30}, - {"name": "Bob", "city": "NYC"}, // 缺少age字段 - {"age": 25, "city": "LA"}, // 缺少name字段 + {"name": "Bob", "city": "NYC"}, // The age field is missing + {"age": 25, "city": "LA"}, // The name field is missing } assert.NotPanics(t, func() { PrintTableFromSlice(dataWithMissingFields, nil) }, "数据行字段缺失不应该panic") - // 测试短字段名(测试最小宽度4的逻辑) + // Test short field name (test minimum width 4 logic) shortFieldData := []map[string]any{ {"a": "1", "bb": "22", "ccc": "333"}, } @@ -119,7 +119,7 @@ func TestPrintTableFromSliceEdgeCases(t *testing.T) { PrintTableFromSlice(shortFieldData, nil) }, "短字段名不应该panic") - // 测试空值和nil值 + // Test null values and nil values nilValueData := []map[string]any{ {"name": "Alice", "value": nil}, {"name": "Bob", "value": ""}, diff --git a/utils/timex/time_test.go b/utils/timex/time_test.go index 6d5f5ba..079962d 100644 --- a/utils/timex/time_test.go +++ b/utils/timex/time_test.go @@ -68,7 +68,7 @@ func TestAlignTimeToWindow(t *testing.T) { } } -// TestAlignTime 测试 AlignTime 函数 +// TestAlignTime tests the AlignTime function func TestAlignTime(t *testing.T) { tests := []struct { name string @@ -159,9 +159,9 @@ func TestAlignTime(t *testing.T) { } } -// TestAlignTimeEdgeCases 测试 AlignTime 函数的边界情况 +// TestAlignTimeEdgeCases tests the boundary state of the AlignTime function func TestAlignTimeEdgeCases(t *testing.T) { - // 测试零时间 + // Test zero time zeroTime := time.Time{} result := AlignTime(zeroTime, time.Minute, true) expected := zeroTime.Truncate(time.Minute) @@ -169,7 +169,7 @@ func TestAlignTimeEdgeCases(t *testing.T) { t.Errorf("AlignTime with zero time failed: expected %v, got %v", expected, result) } - // 测试非常小的时间单位 + // Testing is a very small unit of time testTime := time.Date(2024, 1, 1, 12, 35, 45, 123456789, time.UTC) result = AlignTime(testTime, time.Nanosecond, true) expected = testTime.Truncate(time.Nanosecond) @@ -177,24 +177,24 @@ func TestAlignTimeEdgeCases(t *testing.T) { t.Errorf("AlignTime with nanosecond failed: expected %v, got %v", expected, result) } - // 测试非常大的时间单位 - result = AlignTime(testTime, 365*24*time.Hour, false) // 一年 + // Testing is a very large unit of time + result = AlignTime(testTime, 365*24*time.Hour, false) // One year expected = testTime.Truncate(365 * 24 * time.Hour) if !result.Equal(expected) { t.Errorf("AlignTime with year unit failed: expected %v, got %v", expected, result) } } -// TestAlignTimeToWindowEdgeCases 测试 AlignTimeToWindow 函数的边界情况 +// TestAlignTimeToWindowEdgeCases Tests the boundary status of the AlignTimeToWindow function func TestAlignTimeToWindowEdgeCases(t *testing.T) { - // 测试零时间 + // Test zero time zeroTime := time.Time{} result := AlignTimeToWindow(zeroTime, time.Minute) if !result.Equal(zeroTime) { t.Errorf("AlignTimeToWindow with zero time failed: expected %v, got %v", zeroTime, result) } - // 测试非常小的窗口大小 + // Test with very small window sizes testTime := time.Date(2024, 1, 1, 12, 35, 45, 123456789, time.UTC) result = AlignTimeToWindow(testTime, time.Nanosecond) expected := testTime.Add(time.Duration(-testTime.UnixNano() % int64(time.Nanosecond))) @@ -202,7 +202,7 @@ func TestAlignTimeToWindowEdgeCases(t *testing.T) { t.Errorf("AlignTimeToWindow with nanosecond failed: expected %v, got %v", expected, result) } - // 测试窗口大小为1秒的情况 + // Test window size with a size of 1 second result = AlignTimeToWindow(testTime, time.Second) expectedNano := testTime.UnixNano() - (testTime.UnixNano() % int64(time.Second)) expected = time.Unix(0, expectedNano) @@ -211,16 +211,16 @@ func TestAlignTimeToWindowEdgeCases(t *testing.T) { } } -// TestTimeFunctionsConcurrency 测试时间函数的并发安全性 +// TestTimeFunctionsConcurrency: Tests the concurrency security of the time function func TestTimeFunctionsConcurrency(t *testing.T) { testTime := time.Date(2024, 1, 1, 12, 35, 45, 123456789, time.UTC) - // 启动多个 goroutine 并发调用时间函数 + // Start multiple goroutines to call the time function concurrently done := make(chan bool, 20) for i := 0; i < 20; i++ { go func() { for j := 0; j < 100; j++ { - // 测试 AlignTimeToWindow + // Test AlignTimeToWindow result1 := AlignTimeToWindow(testTime, time.Minute) expected1 := testTime.Add(time.Duration(-testTime.UnixNano() % int64(time.Minute))) if !result1.Equal(expected1) { @@ -228,7 +228,7 @@ func TestTimeFunctionsConcurrency(t *testing.T) { return } - // 测试 AlignTime + // Test AlignTime result2 := AlignTime(testTime, time.Minute, true) expected2 := testTime.Truncate(time.Minute).Add(time.Minute) if !result2.Equal(expected2) { @@ -240,7 +240,7 @@ func TestTimeFunctionsConcurrency(t *testing.T) { }() } - // 等待所有 goroutine 完成 + // Wait for all goroutines to complete for i := 0; i < 20; i++ { <-done } diff --git a/window/counting_window.go b/window/counting_window.go index e522ee4..84d3bf7 100644 --- a/window/counting_window.go +++ b/window/counting_window.go @@ -275,7 +275,7 @@ func (cw *CountingWindow) Stop() { cw.mu.Unlock() if !stopped { - // 由 ctx 取消退出,不 close triggerChan 以免与并发 Add 发送冲突 + // Cancel exit via ctx without closing triggerChan to avoid conflicts with concurrent Add sending cw.cancelFunc() } } diff --git a/window/counting_window_ttl_test.go b/window/counting_window_ttl_test.go index 83538b9..92b5bae 100644 --- a/window/counting_window_ttl_test.go +++ b/window/counting_window_ttl_test.go @@ -9,8 +9,8 @@ import ( "github.com/stretchr/testify/require" ) -// TestCountingWindowStateTTL_ReapsIdleKeys: TTL>0,未攒满且超过 TTL 的死 key 被清理。 -// 用大 TTL 避免后台 ticker 干扰,直接调 reapIdleKeys 精确验证清理逻辑。 +// TestCountingWindowStateTTL_ReapsIdleKeys: TTL>0, dead keys that are not fully saved and exceed TTL are cleared. +// Use large TTL to avoid background ticker interference; directly call reapIdleKeys to precisely verify the cleaning logic. func TestCountingWindowStateTTL_ReapsIdleKeys(t *testing.T) { config := types.WindowConfig{ Type: TypeCounting, @@ -41,7 +41,7 @@ func TestCountingWindowStateTTL_ReapsIdleKeys(t *testing.T) { cw.mu.Unlock() } -// TestCountingWindowStateTTL_KeepsActiveKeys: TTL>0,TTL 内活跃的 key 不被误清。 +// TestCountingWindowStateTTL_KeepsActiveKeys: TTL>0, active keys within TTL are not mistakenly cleared. func TestCountingWindowStateTTL_KeepsActiveKeys(t *testing.T) { config := types.WindowConfig{ Type: TypeCounting, @@ -69,7 +69,7 @@ func TestCountingWindowStateTTL_KeepsActiveKeys(t *testing.T) { cw.mu.Unlock() } -// TestCountingWindowStateTTL_DisabledByDefault: TTL=0(默认)不启动清理,keyedBuffer 正常累积。 +// TestCountingWindowStateTTL_DisabledByDefault: TTL=0 (default) does not start cleanup, but keyedBuffer accumulates normally. func TestCountingWindowStateTTL_DisabledByDefault(t *testing.T) { config := types.WindowConfig{ Type: TypeCounting, diff --git a/window/eventtime_tumbling_test.go b/window/eventtime_tumbling_test.go index 073d808..30f222a 100644 --- a/window/eventtime_tumbling_test.go +++ b/window/eventtime_tumbling_test.go @@ -133,9 +133,9 @@ func TestEventTimeTumblingLateData(t *testing.T) { assert.Equal(t, 1, openWindows) } -// 两次 late update 不得重复累计:第 2 次迟到更新只应新增本次迟到行,不能把第 1 次 -// 迟到行再算一遍。曾因 snapshotData 滚动合并 + tw.data 未驱逐迟到行,每次迟到更新都 -// 把前序迟到行从 tw.data 再读一遍 → COUNT 翻倍(3→4→6,应为 5)。 +// Two late updates must not be accumulated repeatedly: the second late update should only add the current late line; the first update cannot be added +// If you're late, count again. Previously, because snapshotData rolled merge + tw.data failed to evict the late line, every late update was +// Read the preorder delayed line from tw.data again, → COUNT doubles (3→4→6, should be 5). func TestEventTimeTumblingLateDataNoDoubleCount(t *testing.T) { tw := newEventTimeTumbling(t, 2*time.Second, 500*time.Millisecond, 5*time.Second) tw.Start() @@ -146,7 +146,7 @@ func TestEventTimeTumblingLateDataNoDoubleCount(t *testing.T) { tw.Add(etRow(base, 1)) tw.Add(etRow(base.Add(500*time.Millisecond), 2)) - tw.Add(etRow(base.Add(3*time.Second), 3)) // 推进 watermark,触发 [base, base+2s) + tw.Add(etRow(base.Add(3*time.Second), 3)) // Advance watermark, trigger [base, base+2s) recv := func() []types.Row { t.Helper() @@ -159,18 +159,18 @@ func TestEventTimeTumblingLateDataNoDoubleCount(t *testing.T) { } } - require.Len(t, recv(), 2) // 初始触发:原始 2 行 + require.Len(t, recv(), 2) // Initial trigger: Original 2 lines - tw.Add(etRow(base.Add(250*time.Millisecond), 99)) // 第 1 次迟到 - require.Len(t, recv(), 3) // 原始 2 + 迟到 1 = 3 + tw.Add(etRow(base.Add(250*time.Millisecond), 99)) // The first time I was late + require.Len(t, recv(), 3) // Original 2 + late 1 = 3 - tw.Add(etRow(base.Add(750*time.Millisecond), 88)) // 第 2 次迟到 - require.Len(t, recv(), 4) // 原始 2 + e1 + e2 = 4(修复前=5,e1 被重复算) + tw.Add(etRow(base.Add(750*time.Millisecond), 88)) // Second late + require.Len(t, recv(), 4) // Original 2 + e1 + e2 = 4 (before repair = 5, e1 is counted repeatedly) } -// W2: 释放锁做回调之前,currentSlot 必须已推进到下一窗口(且窗口已登记进 triggeredWindows)。 -// 否则释放锁期间并发 Add 会把迟到行孤立到已触发窗口。用回调里观察 currentSlot 确定性验证, -// 无需复现竞态。 +// W2: Before releasing the lock to perform a callback, currentSlot must have been advanced to the next window (and the window has been registered into triggeredWindows). +// Otherwise, during lock release, concurrent Add will isolate the delayed line to the triggered window. Verify determinism by observing currentSlot in callbacks, +// No need to reproduce racing patterns. func TestEventTimeTumblingCurrentSlotAdvancedBeforeCallback(t *testing.T) { tw := newEventTimeTumbling(t, 2*time.Second, 500*time.Millisecond, 5*time.Second) size := 2 * time.Second @@ -183,7 +183,7 @@ func TestEventTimeTumblingCurrentSlotAdvancedBeforeCallback(t *testing.T) { cs := tw.currentSlot open := len(tw.triggeredWindows) tw.mu.RUnlock() - // 回调执行时:currentSlot 应已越过触发窗口,且窗口已登记为可接收迟到补发。 + // When the callback is executed: currentSlot should have passed the trigger window, and the window has been registered as acceptable for late reshipment. if cs != nil && !cs.Start.Before(triggeredEnd) && open > 0 { atomic.StoreInt32(&advanced, 1) } @@ -192,7 +192,7 @@ func TestEventTimeTumblingCurrentSlotAdvancedBeforeCallback(t *testing.T) { defer tw.Stop() tw.Add(etRow(base, 1)) - tw.Add(etRow(base.Add(3 * time.Second), 2)) // 触发 [base, base+2s) + tw.Add(etRow(base.Add(3*time.Second), 2)) // Trigger [base, base+2s) deadline := time.Now().Add(2 * time.Second) for time.Now().Before(deadline) && atomic.LoadInt32(&advanced) == 0 { diff --git a/window/performance_test.go b/window/performance_test.go index 4e27af4..3718991 100644 --- a/window/performance_test.go +++ b/window/performance_test.go @@ -8,9 +8,9 @@ import ( "github.com/rulego/streamsql/types" ) -// TestTumblingWindowPerformance 测试滚动窗口的性能 +// TestTumblingWindowPerformance Tests the performance of the rolling window func TestTumblingWindowPerformance(t *testing.T) { - // 测试不同缓冲区大小的性能 + // Testing performance with different buffer sizes bufferSizes := []int{10, 100, 1000, 5000} for _, bufferSize := range bufferSizes { @@ -23,7 +23,7 @@ func TestTumblingWindowPerformance(t *testing.T) { go tw.Start() - // 模拟高频数据输入 + // Simulates high-frequency data input dataCount := 10000 startTime := time.Now() baseTime := time.Now() @@ -35,23 +35,23 @@ func TestTumblingWindowPerformance(t *testing.T) { }) } - // 等待处理完成 + // Wait for processing to complete time.Sleep(2 * time.Second) - // 获取统计信息 + // Get statistics stats := tw.GetStats() elapsed := time.Since(startTime) - t.Logf("缓冲区大小: %d", bufferSize) - t.Logf("处理时间: %v", elapsed) - t.Logf("发送成功: %d", stats["sentCount"]) - t.Logf("丢弃数量: %d", stats["droppedCount"]) - t.Logf("缓冲区利用率: %d/%d", stats["bufferUsed"], stats["bufferSize"]) + t.Logf("Buffer size: %d", bufferSize) + t.Logf("Processing time: %v", elapsed) + t.Logf("Sent successfully: %d", stats["sentCount"]) + t.Logf("Discarded quantity: %d", stats["droppedCount"]) + t.Logf("Buffer Utilization: %d/%d", stats["bufferUsed"], stats["bufferSize"]) - // 验证没有严重的数据丢失 + // No serious data loss was verified if bufferSize >= 1000 { - if stats["droppedCount"] > int64(dataCount/10) { // 允许最多10%的丢失 - t.Errorf("丢失数据过多: %d (总数: %d)", stats["droppedCount"], dataCount) + if stats["droppedCount"] > int64(dataCount/10) { // Allows up to 10% loss + t.Errorf("Excessive data loss: %d (Total: %d)", stats["droppedCount"], dataCount) } } @@ -60,13 +60,13 @@ func TestTumblingWindowPerformance(t *testing.T) { } } -// TestData 测试数据结构 +// TestData Tests data structures type TestData struct { Ts time.Time tag string } -// BenchmarkTumblingWindowThroughput 测试滚动窗口的吞吐量 +// BenchmarkTumblingWindowThroughput tests the throughput of the rolling window func BenchmarkTumblingWindowThroughput(b *testing.B) { tw, _ := NewTumblingWindow(types.WindowConfig{ Type: "TumblingWindow", @@ -76,7 +76,7 @@ func BenchmarkTumblingWindowThroughput(b *testing.B) { go tw.Start() - // 在后台消费结果,避免阻塞 + // Backend consumption results to avoid blockages done := make(chan struct{}) go func() { for { @@ -107,17 +107,17 @@ func BenchmarkTumblingWindowThroughput(b *testing.B) { } }) - // 获取最终统计 + // Get the final stats stats := tw.GetStats() - b.Logf("发送成功: %d, 丢弃: %d", stats["sentCount"], stats["droppedCount"]) + b.Logf("Send successfully: %d, Discard: %d", stats["sentCount"], stats["droppedCount"]) tw.Stop() close(done) } -// TestWindowBufferOverflow 测试缓冲区溢出处理 +// TestWindowBufferOverflow tests buffer overflow handling func TestWindowBufferOverflow(t *testing.T) { - // 创建一个小缓冲区的窗口 + // Create a window with a small buffer tw, _ := NewTumblingWindow(types.WindowConfig{ Type: "TumblingWindow", Params: []any{50 * time.Millisecond}, @@ -126,8 +126,8 @@ func TestWindowBufferOverflow(t *testing.T) { go tw.Start() - // 不消费输出,导致缓冲区满 - // 只添加数据,不读取输出通道 + // No consumption of output, causing the buffer to be full + // Only add data, do not read the output channel baseTime := time.Now() for i := 0; i < 100; i++ { @@ -137,20 +137,20 @@ func TestWindowBufferOverflow(t *testing.T) { }) } - // 等待一段时间让窗口触发 + // Wait a while for the window to trigger time.Sleep(200 * time.Millisecond) stats := tw.GetStats() - t.Logf("缓冲区溢出测试 - 发送: %d, 丢弃: %d", stats["sentCount"], stats["droppedCount"]) + t.Logf("Buffer Overflow Test - Send: %d, Discard: %d", stats["sentCount"], stats["droppedCount"]) - // 应该有数据被丢弃 + // There should be data discarded if stats["droppedCount"] == 0 { - t.Log("预期会有数据丢弃,但实际没有丢弃") + t.Log("Data was expected to be discarded, but in reality, it was not") } - // 验证系统仍然运行正常(没有阻塞) + // Verify that the system is still running normally (no blocking) if stats["sentCount"] == 0 { - t.Error("应该至少发送了一些数据") + t.Error("At least some data should have been sent") } tw.Stop() diff --git a/window/session_window_regression_test.go b/window/session_window_regression_test.go index c724927..0c39c87 100644 --- a/window/session_window_regression_test.go +++ b/window/session_window_regression_test.go @@ -9,10 +9,10 @@ import ( "github.com/stretchr/testify/require" ) -// TestSessionWindowReapsTriggeredSessionsPT 锁定修复:ProcessingTime 模式下会话过期后 -// 保留在 triggeredSessions 等 allowedLateness 迟到数据;closeTime 过期后必须被清理, -// 否则高基数会话 + allowedLateness>0 会让 triggeredSessions 无界增长。 -// 修复前 checkExpiredSessions(PT 路径)漏调 closeExpiredSessions(仅 ET 路径调)。 +// TestSessionWindowReapsTriggeredSessionsPT lock fix: After a session expires in ProcessingTime mode +// Retention of delayed data for allowedLateness such as triggeredSessions; closeTime must be cleaned up after expiration, +// Otherwise, high base sessions + allowedLateness>0 will cause triggeredSessions to grow unbounded. +// Fixed missed call on checkExpiredSessions (PT path) before closeExpiredSessions (only on ET path). func TestSessionWindowReapsTriggeredSessionsPT(t *testing.T) { config := types.WindowConfig{ Type: TypeSession, @@ -26,7 +26,7 @@ func TestSessionWindowReapsTriggeredSessionsPT(t *testing.T) { sw.Add(map[string]any{"user_id": "u1", "value": 1}) - // 等 会话过期(60ms) + 保留期过期(100ms) + ticker(timeout/2=30ms)多次检查 + 余量。 + // Etc. Session expired (60ms) + Retention expired (100ms) + ticker (timeout/2=30ms) Multiple checks + remaining balance. time.Sleep(500 * time.Millisecond) sw.mu.Lock() diff --git a/window/sliding_window_test.go b/window/sliding_window_test.go index ebfe1b5..8f00c46 100644 --- a/window/sliding_window_test.go +++ b/window/sliding_window_test.go @@ -36,7 +36,7 @@ func TestSlidingWindow(t *testing.T) { }) sw.Start() - // 添加数据 + // Add data t_3 := TestDate{Ts: time.Date(2025, 4, 7, 16, 46, 56, 789000000, time.UTC), tag: "1"} t_2 := TestDate{Ts: time.Date(2025, 4, 7, 16, 46, 57, 789000000, time.UTC), tag: "2"} t_1 := TestDate{Ts: time.Date(2025, 4, 7, 16, 46, 58, 789000000, time.UTC), tag: "3"} @@ -47,13 +47,13 @@ func TestSlidingWindow(t *testing.T) { sw.Add(t_1) sw.Add(t_0) - // 验证每个窗口的数据 - // 处理时间窗口对齐到 epoch(slide 边界):t_3=16:46:56.789 对齐到 16:46:56.000 - // 窗口大小2秒,滑动步长1秒 - // 第一个窗口: [16:46:56.000, 16:46:58.000) - // 第二个窗口: [16:46:57.000, 16:46:59.000) - // 第三个窗口: [16:46:58.000, 16:47:00.000) - // 第四个窗口: [16:46:59.000, 16:47:01.000) + // Verify data for each window + // Processing time window aligned to epoch(slide boundary):t_3=16:46:56.789 Aligned to 16:46:56.000 + // Window size is 2 seconds, sliding step length is 1 second + // First window: [16:46:56.000, 16:46:58.000) + // Second window: [16:46:57.000, 16:46:59.000) + // Third window: [16:46:58.000, 16:47:00.000) + // Window 4: [16:46:59.000, 16:47:01.000) alignedStart := alignWindowStart(t_3.Ts, sw.slide) expected := []TestResult{ {size: 2, data: []TestDate{t_3, t_2}, start: alignedStart, end: alignedStart.Add(sw.size)}, @@ -61,10 +61,10 @@ func TestSlidingWindow(t *testing.T) { {size: 2, data: []TestDate{t_1, t_0}, start: alignedStart.Add(2 * sw.slide), end: alignedStart.Add(2 * sw.slide).Add(sw.size)}, {size: 1, data: []TestDate{t_0}, start: alignedStart.Add(3 * sw.slide), end: alignedStart.Add(3 * sw.slide).Add(sw.size)}, } - // 等待一段时间,触发窗口 + // Wait a while, then the window will be triggered //time.Sleep(3 * time.Second) - // 检查结果 + // Inspection results // resultsChan := sw.OutputChan() // results := make(chan []types.Row) actual := make([]TestResult, 0) @@ -92,10 +92,10 @@ func TestSlidingWindow(t *testing.T) { END: assert.Equal(t, len(actual), len(expected)) - // 预期结果:保留最近 2 秒内的数据 + // Expected outcome: Data from the last 2 seconds is retained for i, exp := range expected { assert.Equal(t, actual[i].size, exp.size, "窗口 %d 的数据量应该匹配", i+1) - // 移除对齐后,窗口时间应该精确匹配(允许微小的纳秒级误差) + // After removing alignment, window times should match precisely (allowing for tiny nanosecond-level errors) assert.WithinDuration(t, exp.start, actual[i].start, 100*time.Millisecond, "窗口 %d 的开始时间应该匹配,预期: %v, 实际: %v", i+1, exp.start, actual[i].start) assert.WithinDuration(t, exp.end, actual[i].end, 100*time.Millisecond, diff --git a/window/strategy_test.go b/window/strategy_test.go index a53b10c..706150c 100644 --- a/window/strategy_test.go +++ b/window/strategy_test.go @@ -10,10 +10,10 @@ import ( "github.com/stretchr/testify/require" ) -// TestOverflowStrategies 测试不同的缓冲区溢出策略 +// TestOverflowStrategies tests different buffer overflow strategies func TestOverflowStrategies(t *testing.T) { t.Run("CountingWindow_StrategyBlock_Timeout", func(t *testing.T) { - // 配置:窗口大小1(每1条数据触发一次),输出缓冲1,阻塞策略,超时100ms + // Configuration: window size 1 (triggered once per data entry), output buffer 1, blocking policy, timeout 100ms config := types.WindowConfig{ Type: "CountingWindow", Params: []any{1}, // Threshold = 1 @@ -24,7 +24,7 @@ func TestOverflowStrategies(t *testing.T) { OverflowConfig: types.OverflowConfig{ Strategy: types.OverflowStrategyBlock, BlockTimeout: 100 * time.Millisecond, - AllowDataLoss: true, // 允许丢弃统计 + AllowDataLoss: true, // Allow discard statistics }, }, } @@ -34,47 +34,47 @@ func TestOverflowStrategies(t *testing.T) { win.Start() defer win.Stop() - // 1. 发送第1条数据,触发窗口,填充 outputChan (容量1) + // 1. Send the first data entry, trigger the window, and fill outputChan (capacity 1) win.Add(map[string]any{"id": 1}) - // 等待处理 + // Waiting for processing time.Sleep(50 * time.Millisecond) stats := win.GetStats() assert.Equal(t, int64(1), stats["sentCount"]) - assert.Equal(t, int64(1), stats["bufferUsed"]) // 应该还在缓冲区中,因为没人读 + assert.Equal(t, int64(1), stats["bufferUsed"]) // It should still be in the buffer zone because no one is reading it - // 2. 发送第2条数据,触发窗口 - // 此时 outputChan 已满,sendResult 应该阻塞 100ms 然后超时丢弃 + // 2. Send the second data entry to trigger the window + // At this point, outputChan is full, and sendResult should block 100ms before being discarded after timeout win.Add(map[string]any{"id": 2}) - // 等待超时 (100ms) + 处理时间 + // Wait timeout (100ms) + processing time time.Sleep(200 * time.Millisecond) stats = win.GetStats() - // 第1条仍在缓冲区(因为没人读) - // 第2条因为阻塞超时被丢弃 + // Article 1 is still in the buffer zone (because no one reads it) + // Article 2 is discarded due to blocked timeout assert.Equal(t, int64(1), stats["bufferUsed"]) assert.Equal(t, int64(1), stats["droppedCount"]) - // 3. 读取缓冲区中的数据,腾出空间 + // 3. Read data from the buffer to free up space select { case <-win.OutputChan(): - // 读出第1条 + // Read out the first item default: t.Fatal("expected data in output channel") } - // 4. 发送第3条数据 + // 4. Send data for item 3 win.Add(map[string]any{"id": 3}) time.Sleep(50 * time.Millisecond) stats = win.GetStats() - assert.Equal(t, int64(2), stats["sentCount"]) // 第1条和第3条发送成功 - assert.Equal(t, int64(1), stats["droppedCount"]) // 第2条丢弃 + assert.Equal(t, int64(2), stats["sentCount"]) // Items 1 and 3 were successfully sent + assert.Equal(t, int64(1), stats["droppedCount"]) // Article 2: Discard }) t.Run("SessionWindow_StrategyBlock_Timeout", func(t *testing.T) { - // 配置:会话超时50ms,输出缓冲1,阻塞策略,超时50ms + // Configuration: Session timeout 50ms, output buffer 1, blocking policy, timeout 50ms config := types.WindowConfig{ Type: "SessionWindow", Params: []any{"50ms"}, @@ -95,22 +95,22 @@ func TestOverflowStrategies(t *testing.T) { win.Start() defer win.Stop() - // 1. 发送数据,开始一个 session + // 1. Send data and start a session win.Add(map[string]any{"id": 1}) - // 2. 等待 session 超时 (50ms) + 检查周期 (timeout/2 = 25ms) - // 确保 session 被触发并发送到 outputChan + // 2. Wait for session timeout (50ms) + check cycle (timeout/2 = 25ms) + // Ensure the session is triggered and sent to outputChan time.Sleep(100 * time.Millisecond) stats := win.GetStats() assert.Equal(t, int64(1), stats["sentCount"]) assert.Equal(t, int64(1), stats["bufferUsed"]) - // 3. 发送数据开始第二个 session (因为上一个已经结束) + // 3. Send data to start the second session (because the previous session has already ended) win.Add(map[string]any{"id": 2}) - // 4. 等待 session 超时 - // 此时 outputChan 已满,应该阻塞并丢弃 + // 4. Wait for session timeout + // At this point, outputChan is full, so it should be blocked and discarded time.Sleep(150 * time.Millisecond) stats = win.GetStats() @@ -119,7 +119,7 @@ func TestOverflowStrategies(t *testing.T) { }) t.Run("CountingWindow_StrategyDrop", func(t *testing.T) { - // 配置:窗口大小1,输出缓冲1,丢弃策略 + // Configuration: window size 1, output buffer 1, discard policy config := types.WindowConfig{ Type: "CountingWindow", Params: []any{1}, @@ -138,19 +138,19 @@ func TestOverflowStrategies(t *testing.T) { win.Start() defer win.Stop() - // 1. 发送第1条数据,填充 outputChan + // 1. Send the first data entry and fill in outputChan win.Add(map[string]any{"id": 1}) time.Sleep(50 * time.Millisecond) - // 2. 发送第2条数据 - // outputChan 已满,StrategyDrop 会尝试丢弃旧数据(outputChan头部)来放入新数据 + // 2. Send the second data + // outputChan is full, and StrategyDrop will try to discard the old data (outputChan header) to add new data win.Add(map[string]any{"id": 2}) time.Sleep(50 * time.Millisecond) stats := win.GetStats() assert.Equal(t, int64(2), stats["sentCount"]) - // 验证现在缓冲区里是第2条数据 + // Verify that the buffer now contains data number 2 select { case data := <-win.OutputChan(): assert.Len(t, data, 1) @@ -161,7 +161,7 @@ func TestOverflowStrategies(t *testing.T) { }) t.Run("TumblingWindow_StrategyBlock_Timeout", func(t *testing.T) { - // 配置:窗口大小50ms,输出缓冲1,阻塞策略,超时50ms + // Configuration: window size 50ms, output buffer 1, blocking policy, timeout 50ms config := types.WindowConfig{ Type: "TumblingWindow", Params: []any{"50ms"}, @@ -182,23 +182,23 @@ func TestOverflowStrategies(t *testing.T) { win.Start() defer win.Stop() - // 1. 发送数据触发第1个窗口 + // 1. Send data to trigger the first window win.Add(map[string]any{"id": 1}) - // 等待窗口触发 (50ms) + // Wait for window to trigger (50ms) time.Sleep(100 * time.Millisecond) stats := win.GetStats() assert.Equal(t, int64(1), stats["sentCount"]) assert.Equal(t, int64(1), stats["bufferUsed"]) - // 2. 发送数据触发第2个窗口 - // 由于没有读取 outputChan,第2个窗口触发时应该阻塞然后超时 + // 2. Send data to trigger the second window + // Since outputChan was not read, the second window should be blocked and then timed out when triggered win.Add(map[string]any{"id": 2}) - // 等待窗口触发 (50ms) + 阻塞超时 (50ms) + // Wait window trigger (50ms) + block timeout (50ms) time.Sleep(150 * time.Millisecond) stats = win.GetStats() - assert.Equal(t, int64(1), stats["bufferUsed"]) // 仍然只有第1个窗口的数据 - assert.Equal(t, int64(1), stats["droppedCount"]) // 第2个窗口结果被丢弃 + assert.Equal(t, int64(1), stats["bufferUsed"]) // Still only data from window 1 + assert.Equal(t, int64(1), stats["droppedCount"]) // The second window result is discarded }) } diff --git a/window/tumbling_window_test.go b/window/tumbling_window_test.go index 7368eed..cd2ab25 100644 --- a/window/tumbling_window_test.go +++ b/window/tumbling_window_test.go @@ -26,7 +26,7 @@ func TestTumblingWindow(t *testing.T) { // Add data every 1100ms baseTime := time.Date(2025, 4, 7, 16, 46, 0, 0, time.UTC) - // 添加测试数据 + // Add test data for i := 0; i < 5; i++ { data := TestDate{ Ts: baseTime.Add(time.Duration(i) * 1100 * time.Millisecond), @@ -35,21 +35,21 @@ func TestTumblingWindow(t *testing.T) { tw.Add(data) } - // 等待足够长的时间确保所有窗口都被触发 + // Wait long enough to ensure all windows are triggered time.Sleep(6 * time.Second) - // 收集窗口结果 + // Collect window results resultsChan := tw.OutputChan() var all [][]types.Row = make([][]types.Row, 0) - // 收集所有窗口数据(带超时) + // Collect all window data (with timeout) timeout := time.After(5 * time.Second) COLLECT: for { select { case results, ok := <-resultsChan: if !ok { - // 通道已关闭 + // The channel has been closed break COLLECT } all = append(all, results) @@ -57,19 +57,19 @@ COLLECT: break COLLECT } case <-timeout: - t.Logf("超时,收集到 %d 个窗口结果", len(all)) + t.Logf("Timeout, collect results from %d windows", len(all)) break COLLECT case <-ctx.Done(): - t.Logf("上下文取消,收集到 %d 个窗口结果", len(all)) + t.Logf("Context is removed, and results are collected from %d windows", len(all)) break COLLECT } } - // 验证窗口数据(至少应该有一些结果) + // Validate window data (at least some results should be present) require.GreaterOrEqual(t, len(all), 1, "至少应该有1个时间窗口的数据") if len(all) >= 3 { - // 验证每个窗口的数据 + // Verify data for each window expectedWindows := []struct { size int tags []string @@ -84,12 +84,12 @@ COLLECT: expected := expectedWindows[i] require.Len(t, window, expected.size, "窗口 %d 数据数量不匹配", i) - // 验证数据内容 + // Verify the data content for _, row := range window { require.Contains(t, expected.tags, row.Data.(TestDate).tag) } - // 验证时间槽 + // Verify time slots startTime := baseTime.Add(time.Duration(i*2) * time.Second) endTime := startTime.Add(2 * time.Second) require.True(t, window[0].Slot.Start.Equal(startTime) && @@ -98,6 +98,6 @@ COLLECT: } } - // 停止窗口 + // Stop window tw.Stop() } diff --git a/window/window_test.go b/window/window_test.go index 484a780..3af1149 100644 --- a/window/window_test.go +++ b/window/window_test.go @@ -12,7 +12,7 @@ import ( "github.com/stretchr/testify/require" ) -// getTypeString 获取对象的类型字符串表示 +// getTypeString Retrieves the type string representation of the object func getTypeString(obj any) string { if obj == nil { return "" @@ -20,14 +20,14 @@ func getTypeString(obj any) string { return reflect.TypeOf(obj).String() } -// TestWindowEdgeCases 测试窗口的边界条件 +// TestWindowEdgeCases: The boundary condition of the test window func TestWindowEdgeCases(t *testing.T) { t.Run("tumbling window with zero duration", func(t *testing.T) { config := types.WindowConfig{ Params: []any{time.Duration(0)}, } _, err := NewTumblingWindow(config) - // 零持续时间可能是有效的,取决于实现 + // Zero duration may be effective, depending on implementation _ = err }) @@ -36,7 +36,7 @@ func TestWindowEdgeCases(t *testing.T) { Params: []any{-time.Second}, } _, err := NewTumblingWindow(config) - // 负持续时间可能是有效的,取决于实现 + // The duration of negative outcomes may be effective, depending on implementation _ = err }) @@ -45,7 +45,7 @@ func TestWindowEdgeCases(t *testing.T) { Params: []any{time.Duration(0), time.Second}, } _, err := NewSlidingWindow(config) - // 零滑动间隔可能是有效的,取决于实现 + // Zero swipe intervals can be effective, depending on implementation _ = err }) @@ -54,12 +54,12 @@ func TestWindowEdgeCases(t *testing.T) { Params: []any{time.Minute, time.Duration(0)}, } _, err := NewSlidingWindow(config) - // 零滑动间隔可能是有效的,取决于实现 + // Zero swipe intervals can be effective, depending on implementation _ = err }) t.Run("sliding window with slide larger than window", func(t *testing.T) { - // 这种情况可能是有效的,取决于实现 + // This situation may be effective, depending on how it is implemented config := types.WindowConfig{ Params: []any{time.Second, time.Minute}, } @@ -89,7 +89,7 @@ func TestWindowEdgeCases(t *testing.T) { Params: []any{time.Duration(0)}, } _, err := NewSessionWindow(config) - // 零超时可能是有效的,取决于实现 + // Zero timeouts may be effective, depending on implementation _ = err }) @@ -98,12 +98,12 @@ func TestWindowEdgeCases(t *testing.T) { Params: []any{-time.Second}, } _, err := NewSessionWindow(config) - // 负超时可能是有效的,取决于实现 + // Negative timeouts may be effective, depending on implementation _ = err }) } -// TestWindowWithNilCallback 测试窗口使用nil回调函数 +// TestWindowWithNilCallback The test window uses the nil callback function func TestWindowWithNilCallback(t *testing.T) { t.Run("tumbling window with nil callback", func(t *testing.T) { config := types.WindowConfig{ @@ -114,7 +114,7 @@ func TestWindowWithNilCallback(t *testing.T) { require.NotNil(t, window) window.Start() - // 添加数据不应该panic + // Adding data shouldn't panic row := types.Row{ Data: map[string]any{"id": 1}, Timestamp: time.Now(), @@ -175,7 +175,7 @@ func TestWindowWithNilCallback(t *testing.T) { }) } -// TestWindowConcurrency 测试窗口的并发安全性 +// TestWindowConcurrency tests the concurrency security of the window func TestWindowConcurrency(t *testing.T) { t.Run("concurrent add to tumbling window", func(t *testing.T) { var receivedData [][]types.Row @@ -221,7 +221,7 @@ func TestWindowConcurrency(t *testing.T) { wg.Wait() - // 等待窗口处理完成 + // Wait for the window to finish time.Sleep(time.Millisecond * 200) }) @@ -265,7 +265,7 @@ func TestWindowConcurrency(t *testing.T) { var wg sync.WaitGroup - // 一个goroutine添加数据 + // A goroutine adds data wg.Add(1) go func() { defer wg.Done() @@ -279,7 +279,7 @@ func TestWindowConcurrency(t *testing.T) { } }() - // 另一个goroutine停止窗口 + // Another goroutine stops the window wg.Add(1) go func() { defer wg.Done() @@ -291,7 +291,7 @@ func TestWindowConcurrency(t *testing.T) { }) } -// TestWindowMemoryManagement 测试窗口的内存管理 +// TestWindowMemoryManagement The memory management of the test window func TestWindowMemoryManagement(t *testing.T) { t.Run("large data in tumbling window", func(t *testing.T) { var processedCount int @@ -310,7 +310,7 @@ func TestWindowMemoryManagement(t *testing.T) { window.Start() - // 添加大量数据 + // Add a lot of data largeData := make([]byte, 1024*1024) // 1MB for i := range largeData { largeData[i] = byte(i % 256) @@ -327,7 +327,7 @@ func TestWindowMemoryManagement(t *testing.T) { window.Add(row) } - // 等待处理完成 + // Wait for processing to complete time.Sleep(time.Millisecond * 200) }) @@ -352,7 +352,7 @@ func TestWindowMemoryManagement(t *testing.T) { window.Start() - // 快速添加大量小数据 + // Quickly add a large amount of small data for i := 0; i < 1000; i++ { row := types.Row{ Data: map[string]any{"id": i}, @@ -361,12 +361,12 @@ func TestWindowMemoryManagement(t *testing.T) { window.Add(row) } - // 等待处理完成 + // Wait for processing to complete time.Sleep(time.Millisecond * 100) }) } -// TestWindowErrorConditions 测试窗口的错误条件 +// TestWindowErrorConditions The error conditions of the test window func TestWindowErrorConditions(t *testing.T) { t.Run("add to stopped window", func(t *testing.T) { config := types.WindowConfig{ @@ -380,7 +380,7 @@ func TestWindowErrorConditions(t *testing.T) { window.Start() - // 向已停止的窗口添加数据不应该panic + // Adding data to stopped windows should not panic row := types.Row{ Data: map[string]any{"id": 1}, Timestamp: time.Now(), @@ -400,7 +400,7 @@ func TestWindowErrorConditions(t *testing.T) { window.Start() - // 添加包含不可序列化数据的行 + // Add rows containing non-serializable data row := types.Row{ Data: map[string]any{ "id": 1, @@ -424,7 +424,7 @@ func TestWindowErrorConditions(t *testing.T) { window.Start() - // 添加时间戳为零值的行 + // Add rows with zero timestamps row := types.Row{ Data: map[string]any{"id": 1}, Timestamp: time.Time{}, @@ -444,7 +444,7 @@ func TestWindowErrorConditions(t *testing.T) { window.Start() - // 添加未来时间戳的行 + // Add a line for future timestamps row := types.Row{ Data: map[string]any{"id": 1}, Timestamp: time.Now().Add(time.Hour), @@ -464,7 +464,7 @@ func TestWindowErrorConditions(t *testing.T) { window.Start() - // 添加很久以前的时间戳的行 + // Add a line of timestamps from a long time ago row := types.Row{ Data: map[string]any{"id": 1}, Timestamp: time.Now().Add(-time.Hour * 24), @@ -473,7 +473,7 @@ func TestWindowErrorConditions(t *testing.T) { }) } -// TestWindowStatsAndMetrics 测试窗口的统计和指标 +// TestWindowStatsAndMetrics Statistics and metrics for the test window func TestWindowStatsAndMetrics(t *testing.T) { t.Run("get stats from tumbling window", func(t *testing.T) { config := types.WindowConfig{ @@ -485,7 +485,7 @@ func TestWindowStatsAndMetrics(t *testing.T) { } assert.Nil(t, err) - // 获取统计信息不应该panic + // Getting statistics shouldn't be panicking stats := window.GetStats() _ = stats }) @@ -502,14 +502,14 @@ func TestWindowStatsAndMetrics(t *testing.T) { window.Start() - // 添加一些数据 + // Add some data row := types.Row{ Data: map[string]any{"id": 1}, Timestamp: time.Now(), } window.Add(row) - // 重置统计信息不应该panic + // Resetting statistics should not be panicking window.ResetStats() }) @@ -523,7 +523,7 @@ func TestWindowStatsAndMetrics(t *testing.T) { } assert.Nil(t, err) - // 获取输出通道不应该panic + // Getting the output channel should not panic outputChan := window.OutputChan() _ = outputChan }) @@ -534,16 +534,16 @@ func TestWindowStatsAndMetrics(t *testing.T) { } window, err := NewTumblingWindow(config) if err == nil { - // 设置新的回调函数不应该panic + // Setting a new callback function should not be panicking newCallback := func(rows []types.Row) { - // 新的回调逻辑 + // A new pullback logic } window.SetCallback(newCallback) } }) } -// TestWindowWithPerformanceConfig 测试窗口性能配置 +// TestWindowWithPerformanceConfig Configuration of the test window func TestWindowWithPerformanceConfig(t *testing.T) { tests := []struct { name string @@ -677,7 +677,7 @@ func TestWindowWithPerformanceConfig(t *testing.T) { }) } -// TestGetTimestampEdgeCases 测试GetTimestamp函数的边缘情况 +// TestGetTimestampEdgeCases tests the edge conditions of the GetTimestamp function func TestGetTimestampEdgeCases(t *testing.T) { tests := []struct { name string @@ -742,16 +742,16 @@ func TestGetTimestampEdgeCases(t *testing.T) { t.Run(tt.name, func(t *testing.T) { result := GetTimestamp(tt.data, tt.tsProp, tt.timeUnit) if tt.checkNow { - // 检查返回的时间是否接近当前时间(允许1秒误差) + // Check if the return time is close to the current time (allow a 1-second margin of error) assert.WithinDuration(t, time.Now(), result, time.Second) } }) } } -// TestExtractTimestampNumericEpochs 校验 event-time 时间戳提取接受全部数值族 -// (JSON 把数字解码成 float64,Go 整数字面量是 int)与数字字符串,且需非零 TimeUnit。 -// 无 TimeUnit 的数值 epoch 因 s/ms 歧义被拒(丢行)。 +// TestExtractTimestampNumericEpochs verifies event-time timestamp extraction accepts all value families +// (JSON decodes the numbers as float64, and the Go integer face is int) and the numeric string, which must be non-zero TimeUnit. +// Values without TimeUnit are rejected (line dropped) due to s/ms ambiguity. func TestExtractTimestampNumericEpochs(t *testing.T) { const msEpoch int64 = 1700000000000 // 2023-11-14T22:13:20Z want := time.Unix(0, msEpoch*int64(time.Millisecond)) @@ -784,9 +784,9 @@ func TestExtractTimestampNumericEpochs(t *testing.T) { } } -// TestNewWindowRejectsNonPositiveDuration 校验构造函数拒绝非正 duration, -// 避免 NewTicker(<=0) 在 Add/Start 里 panic 拖垮进程(SQL 路径由 validateWindowParams -// 兜底,这里覆盖直接构造路径)。 +// TestNewWindowRejectsNonPositiveDuration The validation constructor rejects non-positive durations, +// Avoid NewTicker (<=0) in Add/Start that can cause a process to crash (SQL path is validateWindowParams +// Bottom line, here overlays the direct construction path). func TestNewWindowRejectsNonPositiveDuration(t *testing.T) { cases := []struct { name string @@ -812,8 +812,8 @@ func TestNewWindowRejectsNonPositiveDuration(t *testing.T) { } } -// TestEventTimeWindowDropsUnplaceableLateData 校验 event-time 窗口丢弃无法落位的 -// 迟到行(AllowedLateness=0 默认),避免 tw.data/sw.data 无界增长 OOM。 +// TestEventTimeWindowDropsUnplaceableLateData Checks event-time window discards those that cannot be placed +// Late-arriving line (AllowedLateness=0 default) to avoid unbounded growth of tw.data/sw.data OOM. func TestEventTimeWindowDropsUnplaceableLateData(t *testing.T) { mkWindow := func(typ string) Window { params := []any{time.Second} @@ -886,8 +886,8 @@ func TestWatermarkIgnoresFarFutureTimestamp(t *testing.T) { "real event judged late after a corrupt far-future event") } -// TestSessionWindowDropsLateEvent 校验 event-time session 窗口丢弃不可吸收的迟到行, -// 而非按陈旧 timestamp 建立即刻过期的伪单事件会话。 +// TestSessionWindowDropsLateEvent Validation: The event-time session window discards late, unabsorbable lines, +// Instead of creating an expired fake order event session based on outdated timestamps. func TestSessionWindowDropsLateEvent(t *testing.T) { cfg := types.WindowConfig{ Type: TypeSession, Params: []any{time.Hour}, // long timeout keeps the in-order session alive @@ -911,7 +911,7 @@ func TestSessionWindowDropsLateEvent(t *testing.T) { assert.True(t, n <= 2, "late events must not create sessions, got %d", n) } -// TestSessionWindowSessionKey 测试会话窗口的会话键提取 +// TestSessionWindowSessionKey tests session key extraction for the session window func TestSessionWindowSessionKey(t *testing.T) { config := types.WindowConfig{ Type: TypeSession, @@ -922,7 +922,7 @@ func TestSessionWindowSessionKey(t *testing.T) { sw, err := NewSessionWindow(config) assert.NoError(t, err) - // 测试不同类型的数据 + // Test different types of data tests := []struct { name string data any @@ -949,7 +949,7 @@ func TestSessionWindowSessionKey(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - // 这里只是测试Add方法不会panic + // This is just testing that the Add method does not panic assert.NotPanics(t, func() { sw.Add(tt.data) }) @@ -957,7 +957,7 @@ func TestSessionWindowSessionKey(t *testing.T) { } } -// TestWindowStopBeforeStart 测试在启动前停止窗口 +// TestWindowStopBeforeStart Tests the window that stops before starting func TestWindowStopBeforeStart(t *testing.T) { tests := []struct { name string @@ -998,7 +998,7 @@ func TestWindowStopBeforeStart(t *testing.T) { window, err := CreateWindow(tt.config) assert.NoError(t, err) - // 在启动前停止窗口应该不会panic + // The stop window before startup should not panic assert.NotPanics(t, func() { if tw, ok := window.(*TumblingWindow); ok { tw.Stop() @@ -1015,7 +1015,7 @@ func TestWindowStopBeforeStart(t *testing.T) { } } -// TestWindowMultipleStops 测试多次停止窗口 +// TestWindowMultipleStops Tests multiple stops windows func TestWindowMultipleStops(t *testing.T) { config := types.WindowConfig{ Type: TypeTumbling, @@ -1027,7 +1027,7 @@ func TestWindowMultipleStops(t *testing.T) { tw.Start() - // 多次停止应该不会panic + // Stopping multiple times shouldn't cause panic assert.NotPanics(t, func() { tw.Stop() tw.Stop() @@ -1035,7 +1035,7 @@ func TestWindowMultipleStops(t *testing.T) { }) } -// TestWindowAddAfterStop 测试停止后添加数据 +// TestWindowAddAfterStop: Adds data after the test stops func TestWindowAddAfterStop(t *testing.T) { config := types.WindowConfig{ Type: TypeTumbling, @@ -1048,13 +1048,13 @@ func TestWindowAddAfterStop(t *testing.T) { tw.Start() tw.Stop() - // 停止后添加数据应该不会panic + // Adding data after stopping should not cause panic assert.NotPanics(t, func() { tw.Add(map[string]any{"value": 42}) }) } -// TestCountingWindowWithCallback 测试计数窗口的回调功能 +// TestCountingWindowWithCallback: The callback function of the test counting window func TestCountingWindowWithCallback(t *testing.T) { var mu sync.Mutex callbackData := make([][]types.Row, 0) @@ -1076,14 +1076,14 @@ func TestCountingWindowWithCallback(t *testing.T) { cw.Start() // CountingWindow doesn't have Stop method, will be handled by context cancellation - // 添加数据 + // Add data cw.Add(map[string]any{"value": 1}) cw.Add(map[string]any{"value": 2}) - // 等待处理 + // Waiting for processing time.Sleep(100 * time.Millisecond) - // 检查回调是否被调用 + // Check if the callback has been called assert.Eventually(t, func() bool { mu.Lock() defer mu.Unlock() @@ -1091,7 +1091,7 @@ func TestCountingWindowWithCallback(t *testing.T) { }, time.Second, 10*time.Millisecond) } -// TestSlidingWindowInvalidParams 测试滑动窗口的无效参数 +// TestSlidingWindowInvalidParams Tests invalid parameters for the slider func TestSlidingWindowInvalidParams(t *testing.T) { tests := []struct { name string @@ -1119,7 +1119,7 @@ func TestSlidingWindowInvalidParams(t *testing.T) { } } -// TestWindowUnifiedConfigIntegration 集成测试:验证窗口配置与实际数据处理的集成 +// TestWindowUnifiedConfigIntegration Integration Testing: Verifies the integration of window configuration with actual data processing func TestWindowUnifiedConfigIntegration(t *testing.T) { t.Run("性能配置集成测试", func(t *testing.T) { performanceConfig := types.HighPerformanceConfig() @@ -1134,13 +1134,13 @@ func TestWindowUnifiedConfigIntegration(t *testing.T) { assert.NoError(t, err) defer tw.Stop() - // 验证缓冲区大小 + // Verify the buffer size assert.Equal(t, 200, cap(tw.outputChan)) - // 启动窗口 + // Startup window tw.Start() - // 发送测试数据 + // Send test data for i := 0; i < 10; i++ { tw.Add(map[string]any{ "id": i, @@ -1148,24 +1148,24 @@ func TestWindowUnifiedConfigIntegration(t *testing.T) { }) } - // 等待窗口触发 + // Wait for the window to trigger time.Sleep(1200 * time.Millisecond) - // 验证窗口能正常工作 + // The verification window works properly select { case data := <-tw.OutputChan(): assert.Greater(t, len(data), 0) assert.LessOrEqual(t, len(data), 10) case <-time.After(500 * time.Millisecond): - t.Error("超时未接收到窗口输出") + t.Error("No window output received after timeout") } }) t.Run("缓冲区溢出处理", func(t *testing.T) { - // 创建一个小缓冲区的窗口 + // Create a window with a small buffer smallBufferConfig := types.PerformanceConfig{ BufferConfig: types.BufferConfig{ - WindowOutputSize: 1, // 非常小的缓冲区 + WindowOutputSize: 1, // A very small buffer zone }, } @@ -1181,22 +1181,22 @@ func TestWindowUnifiedConfigIntegration(t *testing.T) { tw.Start() defer tw.Stop() - // 快速添加大量数据,可能导致缓冲区溢出 + // Quickly adding large amounts of data may cause buffer overflow for i := 0; i < 10; i++ { tw.Add(map[string]any{"value": i}) } - // 等待处理 + // Waiting for processing time.Sleep(200 * time.Millisecond) - // 检查统计信息 + // Check the statistics stats := tw.GetStats() assert.Contains(t, stats, "droppedCount") assert.Contains(t, stats, "sentCount") }) } -// TestCreateWindow 测试窗口工厂函数 +// TestCreateWindow TestWindow Factory function func TestCreateWindow(t *testing.T) { tests := []struct { name string @@ -1278,7 +1278,7 @@ func TestCreateWindow(t *testing.T) { assert.NotNil(t, window) assert.Equal(t, tt.expectedType, getTypeString(window)) - // 验证窗口能正常工作 + // The verification window works properly if closer, ok := window.(interface{ Stop() }); ok { closer.Stop() } @@ -1286,7 +1286,7 @@ func TestCreateWindow(t *testing.T) { } } -// TestGetTimestampCoverage 测试时间戳提取函数 +// TestGetTimestampCoverage tests the timestamp extraction function func TestGetTimestampCoverage(t *testing.T) { testTime := time.Date(2025, 1, 1, 12, 0, 0, 0, time.UTC) @@ -1346,7 +1346,7 @@ func TestGetTimestampCoverage(t *testing.T) { t.Run(tt.name, func(t *testing.T) { result := GetTimestamp(tt.data, tt.tsProp, tt.timeUnit) if tt.name == "无法提取时间戳,使用当前时间" { - // 检查返回的时间是否接近当前时间(允许1秒误差) + // Check if the return time is close to the current time (allow a 1-second margin of error) assert.WithinDuration(t, time.Now(), result, time.Second) } else { assert.Equal(t, tt.expected, result) @@ -1355,7 +1355,7 @@ func TestGetTimestampCoverage(t *testing.T) { } } -// TestWindowErrorHandling 测试窗口错误处理 +// TestWindowErrorHandling: Test window error handling func TestWindowErrorHandling(t *testing.T) { t.Run("滚动窗口无效大小", func(t *testing.T) { config := types.WindowConfig{ @@ -1394,7 +1394,7 @@ func TestWindowErrorHandling(t *testing.T) { }) } -// TestSessionWindowAdvanced 测试会话窗口的高级功能 +// TestSessionWindowAdvanced is an advanced feature of the test session window func TestSessionWindowAdvanced(t *testing.T) { config := types.WindowConfig{ Type: TypeSession, @@ -1406,16 +1406,16 @@ func TestSessionWindowAdvanced(t *testing.T) { assert.NoError(t, err) assert.NotNil(t, sw) - // 测试设置回调函数 + // Test the callback function sw.SetCallback(func(results []types.Row) { // Callback executed }) - // 启动窗口 + // Startup window sw.Start() defer sw.Stop() - // 添加不同用户的数据 + // Add data from different users sw.Add(map[string]any{ "user_id": "user1", "value": 100, @@ -1426,25 +1426,25 @@ func TestSessionWindowAdvanced(t *testing.T) { "value": 200, }) - // 等待会话超时 + // Waiting for the session to time out time.Sleep(1500 * time.Millisecond) - // 检查输出通道 + // Check the output channel select { case data := <-sw.OutputChan(): assert.NotEmpty(t, data) case <-time.After(500 * time.Millisecond): - // 可能没有数据输出,这也是正常的 + // There may be no data output, which is normal } - // 测试重置功能 + // Test reset function sw.Reset() - // 测试手动触发 + // Testing is triggered manually sw.Trigger() } -// TestSlidingWindowAdvanced 测试滑动窗口的高级功能 +// TestSlidingWindowAdvanced Tests the advanced features of sliding windows func TestSlidingWindowAdvanced(t *testing.T) { config := types.WindowConfig{ Type: TypeSliding, @@ -1457,18 +1457,18 @@ func TestSlidingWindowAdvanced(t *testing.T) { assert.NoError(t, err) assert.NotNil(t, sw) - // 测试获取输出通道 + // Test to obtain the output channel outputChan := sw.OutputChan() assert.NotNil(t, outputChan) - // 测试重置功能 + // Test reset function sw.Reset() - // 测试手动触发 + // Testing is triggered manually sw.Trigger() } -// TestCountingWindowAdvanced 测试计数窗口的高级功能 +// TestCountingWindowAdvanced: Advanced features of the test counting window func TestCountingWindowAdvanced(t *testing.T) { config := types.WindowConfig{ Type: TypeCounting, @@ -1481,16 +1481,16 @@ func TestCountingWindowAdvanced(t *testing.T) { assert.NoError(t, err) assert.NotNil(t, cw) - // 测试设置回调函数 + // Test the callback function cw.SetCallback(func(results []types.Row) { // Callback executed }) - // 启动窗口 + // Startup window cw.Start() // CountingWindow doesn't have Stop method - // 添加数据直到达到阈值 + // Add data until the threshold is reached for i := 0; i < 3; i++ { cw.Add(map[string]any{ "timestamp": time.Now().Unix(), @@ -1498,25 +1498,25 @@ func TestCountingWindowAdvanced(t *testing.T) { }) } - // 等待一段时间让窗口处理数据 + // Wait a while for the window to process the data time.Sleep(100 * time.Millisecond) - // 检查输出通道 + // Check the output channel select { case data := <-cw.OutputChan(): assert.Len(t, data, 3) case <-time.After(500 * time.Millisecond): - // 可能没有数据输出,这也是正常的 + // There may be no data output, which is normal } - // 测试重置功能 + // Test reset function cw.Reset() - // 测试手动触发 + // Testing is triggered manually cw.Trigger() } -// TestTumblingWindowAdvanced 测试滚动窗口的高级功能 +// TestTumblingWindowAdvanced tests the advanced features of rolling windows func TestTumblingWindowAdvanced(t *testing.T) { config := types.WindowConfig{ Type: TypeTumbling, @@ -1529,29 +1529,29 @@ func TestTumblingWindowAdvanced(t *testing.T) { assert.NoError(t, err) assert.NotNil(t, tw) - // 检查统计信息 + // Check the statistics stats := tw.GetStats() assert.Contains(t, stats, "sentCount") assert.Contains(t, stats, "droppedCount") - // 测试重置统计信息 + // Test reset statistics tw.ResetStats() stats = tw.GetStats() assert.Equal(t, int64(0), stats["droppedCount"]) assert.Equal(t, int64(0), stats["sentCount"]) - // 测试设置回调函数 + // Test the callback function tw.SetCallback(func(results []types.Row) { // Callback executed }) - // 测试获取输出通道 + // Test to obtain the output channel outputChan := tw.OutputChan() assert.NotNil(t, outputChan) - // 测试重置功能 + // Test reset function tw.Reset() - // 测试手动触发 + // Testing is triggered manually tw.Trigger() }