diff options
Diffstat (limited to 'weed/query/engine/arithmetic_functions_test.go')
| -rw-r--r-- | weed/query/engine/arithmetic_functions_test.go | 530 |
1 files changed, 530 insertions, 0 deletions
diff --git a/weed/query/engine/arithmetic_functions_test.go b/weed/query/engine/arithmetic_functions_test.go new file mode 100644 index 000000000..8c5e11dec --- /dev/null +++ b/weed/query/engine/arithmetic_functions_test.go @@ -0,0 +1,530 @@ +package engine + +import ( + "testing" + + "github.com/seaweedfs/seaweedfs/weed/pb/schema_pb" +) + +func TestArithmeticOperations(t *testing.T) { + engine := NewTestSQLEngine() + + tests := []struct { + name string + left *schema_pb.Value + right *schema_pb.Value + operator ArithmeticOperator + expected *schema_pb.Value + expectErr bool + }{ + // Addition tests + { + name: "Add two integers", + left: &schema_pb.Value{Kind: &schema_pb.Value_Int64Value{Int64Value: 10}}, + right: &schema_pb.Value{Kind: &schema_pb.Value_Int64Value{Int64Value: 5}}, + operator: OpAdd, + expected: &schema_pb.Value{Kind: &schema_pb.Value_Int64Value{Int64Value: 15}}, + expectErr: false, + }, + { + name: "Add integer and float", + left: &schema_pb.Value{Kind: &schema_pb.Value_Int64Value{Int64Value: 10}}, + right: &schema_pb.Value{Kind: &schema_pb.Value_DoubleValue{DoubleValue: 5.5}}, + operator: OpAdd, + expected: &schema_pb.Value{Kind: &schema_pb.Value_DoubleValue{DoubleValue: 15.5}}, + expectErr: false, + }, + // Subtraction tests + { + name: "Subtract two integers", + left: &schema_pb.Value{Kind: &schema_pb.Value_Int64Value{Int64Value: 10}}, + right: &schema_pb.Value{Kind: &schema_pb.Value_Int64Value{Int64Value: 3}}, + operator: OpSub, + expected: &schema_pb.Value{Kind: &schema_pb.Value_Int64Value{Int64Value: 7}}, + expectErr: false, + }, + // Multiplication tests + { + name: "Multiply two integers", + left: &schema_pb.Value{Kind: &schema_pb.Value_Int64Value{Int64Value: 6}}, + right: &schema_pb.Value{Kind: &schema_pb.Value_Int64Value{Int64Value: 7}}, + operator: OpMul, + expected: &schema_pb.Value{Kind: &schema_pb.Value_Int64Value{Int64Value: 42}}, + expectErr: false, + }, + { + name: "Multiply with float", + left: &schema_pb.Value{Kind: &schema_pb.Value_Int64Value{Int64Value: 5}}, + right: &schema_pb.Value{Kind: &schema_pb.Value_FloatValue{FloatValue: 2.5}}, + operator: OpMul, + expected: &schema_pb.Value{Kind: &schema_pb.Value_DoubleValue{DoubleValue: 12.5}}, + expectErr: false, + }, + // Division tests + { + name: "Divide two integers", + left: &schema_pb.Value{Kind: &schema_pb.Value_Int64Value{Int64Value: 20}}, + right: &schema_pb.Value{Kind: &schema_pb.Value_Int64Value{Int64Value: 4}}, + operator: OpDiv, + expected: &schema_pb.Value{Kind: &schema_pb.Value_DoubleValue{DoubleValue: 5.0}}, + expectErr: false, + }, + { + name: "Division by zero", + left: &schema_pb.Value{Kind: &schema_pb.Value_Int64Value{Int64Value: 10}}, + right: &schema_pb.Value{Kind: &schema_pb.Value_Int64Value{Int64Value: 0}}, + operator: OpDiv, + expected: nil, + expectErr: true, + }, + // Modulo tests + { + name: "Modulo operation", + left: &schema_pb.Value{Kind: &schema_pb.Value_Int64Value{Int64Value: 17}}, + right: &schema_pb.Value{Kind: &schema_pb.Value_Int64Value{Int64Value: 5}}, + operator: OpMod, + expected: &schema_pb.Value{Kind: &schema_pb.Value_Int64Value{Int64Value: 2}}, + expectErr: false, + }, + { + name: "Modulo by zero", + left: &schema_pb.Value{Kind: &schema_pb.Value_Int64Value{Int64Value: 10}}, + right: &schema_pb.Value{Kind: &schema_pb.Value_Int64Value{Int64Value: 0}}, + operator: OpMod, + expected: nil, + expectErr: true, + }, + // String conversion tests + { + name: "Add string number to integer", + left: &schema_pb.Value{Kind: &schema_pb.Value_StringValue{StringValue: "15"}}, + right: &schema_pb.Value{Kind: &schema_pb.Value_Int64Value{Int64Value: 5}}, + operator: OpAdd, + expected: &schema_pb.Value{Kind: &schema_pb.Value_DoubleValue{DoubleValue: 20.0}}, + expectErr: false, + }, + { + name: "Invalid string conversion", + left: &schema_pb.Value{Kind: &schema_pb.Value_StringValue{StringValue: "not_a_number"}}, + right: &schema_pb.Value{Kind: &schema_pb.Value_Int64Value{Int64Value: 5}}, + operator: OpAdd, + expected: nil, + expectErr: true, + }, + // Boolean conversion tests + { + name: "Add boolean to integer", + left: &schema_pb.Value{Kind: &schema_pb.Value_BoolValue{BoolValue: true}}, + right: &schema_pb.Value{Kind: &schema_pb.Value_Int64Value{Int64Value: 5}}, + operator: OpAdd, + expected: &schema_pb.Value{Kind: &schema_pb.Value_DoubleValue{DoubleValue: 6.0}}, + expectErr: false, + }, + // Null value tests + { + name: "Add with null left operand", + left: nil, + right: &schema_pb.Value{Kind: &schema_pb.Value_Int64Value{Int64Value: 5}}, + operator: OpAdd, + expected: nil, + expectErr: true, + }, + { + name: "Add with null right operand", + left: &schema_pb.Value{Kind: &schema_pb.Value_Int64Value{Int64Value: 5}}, + right: nil, + operator: OpAdd, + expected: nil, + expectErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result, err := engine.EvaluateArithmeticExpression(tt.left, tt.right, tt.operator) + + if tt.expectErr { + if err == nil { + t.Errorf("Expected error but got none") + } + return + } + + if err != nil { + t.Errorf("Unexpected error: %v", err) + return + } + + if !valuesEqual(result, tt.expected) { + t.Errorf("Expected %v, got %v", tt.expected, result) + } + }) + } +} + +func TestIndividualArithmeticFunctions(t *testing.T) { + engine := NewTestSQLEngine() + + left := &schema_pb.Value{Kind: &schema_pb.Value_Int64Value{Int64Value: 10}} + right := &schema_pb.Value{Kind: &schema_pb.Value_Int64Value{Int64Value: 3}} + + // Test Add function + result, err := engine.Add(left, right) + if err != nil { + t.Errorf("Add function failed: %v", err) + } + expected := &schema_pb.Value{Kind: &schema_pb.Value_Int64Value{Int64Value: 13}} + if !valuesEqual(result, expected) { + t.Errorf("Add: Expected %v, got %v", expected, result) + } + + // Test Subtract function + result, err = engine.Subtract(left, right) + if err != nil { + t.Errorf("Subtract function failed: %v", err) + } + expected = &schema_pb.Value{Kind: &schema_pb.Value_Int64Value{Int64Value: 7}} + if !valuesEqual(result, expected) { + t.Errorf("Subtract: Expected %v, got %v", expected, result) + } + + // Test Multiply function + result, err = engine.Multiply(left, right) + if err != nil { + t.Errorf("Multiply function failed: %v", err) + } + expected = &schema_pb.Value{Kind: &schema_pb.Value_Int64Value{Int64Value: 30}} + if !valuesEqual(result, expected) { + t.Errorf("Multiply: Expected %v, got %v", expected, result) + } + + // Test Divide function + result, err = engine.Divide(left, right) + if err != nil { + t.Errorf("Divide function failed: %v", err) + } + expected = &schema_pb.Value{Kind: &schema_pb.Value_DoubleValue{DoubleValue: 10.0/3.0}} + if !valuesEqual(result, expected) { + t.Errorf("Divide: Expected %v, got %v", expected, result) + } + + // Test Modulo function + result, err = engine.Modulo(left, right) + if err != nil { + t.Errorf("Modulo function failed: %v", err) + } + expected = &schema_pb.Value{Kind: &schema_pb.Value_Int64Value{Int64Value: 1}} + if !valuesEqual(result, expected) { + t.Errorf("Modulo: Expected %v, got %v", expected, result) + } +} + +func TestMathematicalFunctions(t *testing.T) { + engine := NewTestSQLEngine() + + t.Run("ROUND function tests", func(t *testing.T) { + tests := []struct { + name string + value *schema_pb.Value + precision *schema_pb.Value + expected *schema_pb.Value + expectErr bool + }{ + { + name: "Round float to integer", + value: &schema_pb.Value{Kind: &schema_pb.Value_DoubleValue{DoubleValue: 3.7}}, + precision: nil, + expected: &schema_pb.Value{Kind: &schema_pb.Value_DoubleValue{DoubleValue: 4.0}}, + expectErr: false, + }, + { + name: "Round integer stays integer", + value: &schema_pb.Value{Kind: &schema_pb.Value_Int64Value{Int64Value: 5}}, + precision: nil, + expected: &schema_pb.Value{Kind: &schema_pb.Value_Int64Value{Int64Value: 5}}, + expectErr: false, + }, + { + name: "Round with precision 2", + value: &schema_pb.Value{Kind: &schema_pb.Value_DoubleValue{DoubleValue: 3.14159}}, + precision: &schema_pb.Value{Kind: &schema_pb.Value_Int64Value{Int64Value: 2}}, + expected: &schema_pb.Value{Kind: &schema_pb.Value_DoubleValue{DoubleValue: 3.14}}, + expectErr: false, + }, + { + name: "Round negative number", + value: &schema_pb.Value{Kind: &schema_pb.Value_DoubleValue{DoubleValue: -3.7}}, + precision: nil, + expected: &schema_pb.Value{Kind: &schema_pb.Value_DoubleValue{DoubleValue: -4.0}}, + expectErr: false, + }, + { + name: "Round null value", + value: nil, + precision: nil, + expected: nil, + expectErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var result *schema_pb.Value + var err error + + if tt.precision != nil { + result, err = engine.Round(tt.value, tt.precision) + } else { + result, err = engine.Round(tt.value) + } + + if tt.expectErr { + if err == nil { + t.Errorf("Expected error but got none") + } + return + } + + if err != nil { + t.Errorf("Unexpected error: %v", err) + return + } + + if !valuesEqual(result, tt.expected) { + t.Errorf("Expected %v, got %v", tt.expected, result) + } + }) + } + }) + + t.Run("CEIL function tests", func(t *testing.T) { + tests := []struct { + name string + value *schema_pb.Value + expected *schema_pb.Value + expectErr bool + }{ + { + name: "Ceil positive decimal", + value: &schema_pb.Value{Kind: &schema_pb.Value_DoubleValue{DoubleValue: 3.2}}, + expected: &schema_pb.Value{Kind: &schema_pb.Value_Int64Value{Int64Value: 4}}, + expectErr: false, + }, + { + name: "Ceil negative decimal", + value: &schema_pb.Value{Kind: &schema_pb.Value_DoubleValue{DoubleValue: -3.2}}, + expected: &schema_pb.Value{Kind: &schema_pb.Value_Int64Value{Int64Value: -3}}, + expectErr: false, + }, + { + name: "Ceil integer", + value: &schema_pb.Value{Kind: &schema_pb.Value_Int64Value{Int64Value: 5}}, + expected: &schema_pb.Value{Kind: &schema_pb.Value_Int64Value{Int64Value: 5}}, + expectErr: false, + }, + { + name: "Ceil null value", + value: nil, + expected: nil, + expectErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result, err := engine.Ceil(tt.value) + + if tt.expectErr { + if err == nil { + t.Errorf("Expected error but got none") + } + return + } + + if err != nil { + t.Errorf("Unexpected error: %v", err) + return + } + + if !valuesEqual(result, tt.expected) { + t.Errorf("Expected %v, got %v", tt.expected, result) + } + }) + } + }) + + t.Run("FLOOR function tests", func(t *testing.T) { + tests := []struct { + name string + value *schema_pb.Value + expected *schema_pb.Value + expectErr bool + }{ + { + name: "Floor positive decimal", + value: &schema_pb.Value{Kind: &schema_pb.Value_DoubleValue{DoubleValue: 3.8}}, + expected: &schema_pb.Value{Kind: &schema_pb.Value_Int64Value{Int64Value: 3}}, + expectErr: false, + }, + { + name: "Floor negative decimal", + value: &schema_pb.Value{Kind: &schema_pb.Value_DoubleValue{DoubleValue: -3.2}}, + expected: &schema_pb.Value{Kind: &schema_pb.Value_Int64Value{Int64Value: -4}}, + expectErr: false, + }, + { + name: "Floor integer", + value: &schema_pb.Value{Kind: &schema_pb.Value_Int64Value{Int64Value: 5}}, + expected: &schema_pb.Value{Kind: &schema_pb.Value_Int64Value{Int64Value: 5}}, + expectErr: false, + }, + { + name: "Floor null value", + value: nil, + expected: nil, + expectErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result, err := engine.Floor(tt.value) + + if tt.expectErr { + if err == nil { + t.Errorf("Expected error but got none") + } + return + } + + if err != nil { + t.Errorf("Unexpected error: %v", err) + return + } + + if !valuesEqual(result, tt.expected) { + t.Errorf("Expected %v, got %v", tt.expected, result) + } + }) + } + }) + + t.Run("ABS function tests", func(t *testing.T) { + tests := []struct { + name string + value *schema_pb.Value + expected *schema_pb.Value + expectErr bool + }{ + { + name: "Abs positive integer", + value: &schema_pb.Value{Kind: &schema_pb.Value_Int64Value{Int64Value: 5}}, + expected: &schema_pb.Value{Kind: &schema_pb.Value_Int64Value{Int64Value: 5}}, + expectErr: false, + }, + { + name: "Abs negative integer", + value: &schema_pb.Value{Kind: &schema_pb.Value_Int64Value{Int64Value: -5}}, + expected: &schema_pb.Value{Kind: &schema_pb.Value_Int64Value{Int64Value: 5}}, + expectErr: false, + }, + { + name: "Abs positive double", + value: &schema_pb.Value{Kind: &schema_pb.Value_DoubleValue{DoubleValue: 3.14}}, + expected: &schema_pb.Value{Kind: &schema_pb.Value_DoubleValue{DoubleValue: 3.14}}, + expectErr: false, + }, + { + name: "Abs negative double", + value: &schema_pb.Value{Kind: &schema_pb.Value_DoubleValue{DoubleValue: -3.14}}, + expected: &schema_pb.Value{Kind: &schema_pb.Value_DoubleValue{DoubleValue: 3.14}}, + expectErr: false, + }, + { + name: "Abs positive float", + value: &schema_pb.Value{Kind: &schema_pb.Value_FloatValue{FloatValue: 2.5}}, + expected: &schema_pb.Value{Kind: &schema_pb.Value_FloatValue{FloatValue: 2.5}}, + expectErr: false, + }, + { + name: "Abs negative float", + value: &schema_pb.Value{Kind: &schema_pb.Value_FloatValue{FloatValue: -2.5}}, + expected: &schema_pb.Value{Kind: &schema_pb.Value_FloatValue{FloatValue: 2.5}}, + expectErr: false, + }, + { + name: "Abs zero", + value: &schema_pb.Value{Kind: &schema_pb.Value_Int64Value{Int64Value: 0}}, + expected: &schema_pb.Value{Kind: &schema_pb.Value_Int64Value{Int64Value: 0}}, + expectErr: false, + }, + { + name: "Abs null value", + value: nil, + expected: nil, + expectErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result, err := engine.Abs(tt.value) + + if tt.expectErr { + if err == nil { + t.Errorf("Expected error but got none") + } + return + } + + if err != nil { + t.Errorf("Unexpected error: %v", err) + return + } + + if !valuesEqual(result, tt.expected) { + t.Errorf("Expected %v, got %v", tt.expected, result) + } + }) + } + }) +} + +// Helper function to compare two schema_pb.Value objects +func valuesEqual(v1, v2 *schema_pb.Value) bool { + if v1 == nil && v2 == nil { + return true + } + if v1 == nil || v2 == nil { + return false + } + + switch v1Kind := v1.Kind.(type) { + case *schema_pb.Value_Int32Value: + if v2Kind, ok := v2.Kind.(*schema_pb.Value_Int32Value); ok { + return v1Kind.Int32Value == v2Kind.Int32Value + } + case *schema_pb.Value_Int64Value: + if v2Kind, ok := v2.Kind.(*schema_pb.Value_Int64Value); ok { + return v1Kind.Int64Value == v2Kind.Int64Value + } + case *schema_pb.Value_FloatValue: + if v2Kind, ok := v2.Kind.(*schema_pb.Value_FloatValue); ok { + return v1Kind.FloatValue == v2Kind.FloatValue + } + case *schema_pb.Value_DoubleValue: + if v2Kind, ok := v2.Kind.(*schema_pb.Value_DoubleValue); ok { + return v1Kind.DoubleValue == v2Kind.DoubleValue + } + case *schema_pb.Value_StringValue: + if v2Kind, ok := v2.Kind.(*schema_pb.Value_StringValue); ok { + return v1Kind.StringValue == v2Kind.StringValue + } + case *schema_pb.Value_BoolValue: + if v2Kind, ok := v2.Kind.(*schema_pb.Value_BoolValue); ok { + return v1Kind.BoolValue == v2Kind.BoolValue + } + } + + return false +} |
