diff --git a/backend/v3/storage/database/operators.go b/backend/v3/storage/database/operators.go index c8820d918db..758f244d069 100644 --- a/backend/v3/storage/database/operators.go +++ b/backend/v3/storage/database/operators.go @@ -34,15 +34,21 @@ const ( TextOperationStartsWith // TextOperationStartsWithIgnoreCase checks if the first string starts with the second, ignoring case. TextOperationStartsWithIgnoreCase + // TextOperationContains checks if the first string contains the second + TextOperationContains + // TextOperationContains checks if the first string contains the second, ignoring case. + TextOperationContainsWithIgnoreCase ) var textOperations = map[TextOperation]string{ - TextOperationEqual: " = ", - TextOperationEqualIgnoreCase: " LIKE ", - TextOperationNotEqual: " <> ", - TextOperationNotEqualIgnoreCase: " NOT LIKE ", - TextOperationStartsWith: " LIKE ", - TextOperationStartsWithIgnoreCase: " LIKE ", + TextOperationEqual: " = ", + TextOperationEqualIgnoreCase: " LIKE ", + TextOperationNotEqual: " <> ", + TextOperationNotEqualIgnoreCase: " NOT LIKE ", + TextOperationStartsWith: " LIKE ", + TextOperationStartsWithIgnoreCase: " LIKE ", + TextOperationContains: " LIKE ", + TextOperationContainsWithIgnoreCase: " ILIKE ", } func writeTextOperation[T Text](builder *StatementBuilder, col Column, op TextOperation, value T) { @@ -75,6 +81,12 @@ func writeTextOperation[T Text](builder *StatementBuilder, col Column, op TextOp builder.WriteArg(value) builder.WriteString(")") builder.WriteString(" || '%'") + case TextOperationContains, TextOperationContainsWithIgnoreCase: + col.WriteQualified(builder) + builder.WriteString(textOperations[op]) + builder.WriteString("'%' || ") + builder.WriteArg(value) + builder.WriteString(" || '%'") default: panic("unsupported text operation") } diff --git a/backend/v3/storage/database/operators_test.go b/backend/v3/storage/database/operators_test.go new file mode 100644 index 00000000000..d8c8a4186e7 --- /dev/null +++ b/backend/v3/storage/database/operators_test.go @@ -0,0 +1,106 @@ +package database + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestWriteTextOperation(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + col Column + op TextOperation + value string + expected string + args []any + }{ + { + name: "Equal", + col: NewColumn("test", "col"), + op: TextOperationEqual, + value: "value", + expected: "test.col = $1", + args: []any{"value"}, + }, + { + name: "NotEqual", + col: NewColumn("test", "col"), + op: TextOperationNotEqual, + value: "value", + expected: "test.col <> $1", + args: []any{"value"}, + }, + { + name: "EqualIgnoreCase", + col: NewColumn("test", "col"), + op: TextOperationEqualIgnoreCase, + value: "value", + expected: "LOWER(test.col) LIKE LOWER($1)", + args: []any{"value"}, + }, + { + name: "NotEqualIgnoreCase", + col: NewColumn("test", "col"), + op: TextOperationNotEqualIgnoreCase, + value: "value", + expected: "LOWER(test.col) NOT LIKE LOWER($1)", + args: []any{"value"}, + }, + { + name: "StartsWith", + col: NewColumn("test", "col"), + op: TextOperationStartsWith, + value: "value", + expected: "test.col LIKE $1 || '%'", + args: []any{"value"}, + }, + { + name: "StartsWithIgnoreCase", + col: NewColumn("test", "col"), + op: TextOperationStartsWithIgnoreCase, + value: "value", + expected: "LOWER(test.col) LIKE LOWER($1) || '%'", + args: []any{"value"}, + }, + { + name: "Contains", + col: NewColumn("test", "col"), + op: TextOperationContains, + value: "value", + expected: "test.col LIKE '%' || $1 || '%'", + args: []any{"value"}, + }, + { + name: "ContainsIgnoreCase", + col: NewColumn("test", "col"), + op: TextOperationContainsWithIgnoreCase, + value: "value", + expected: "test.col ILIKE '%' || $1 || '%'", + args: []any{"value"}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + builder := &StatementBuilder{} + writeTextOperation(builder, tt.col, tt.op, tt.value) + + assert.Equal(t, tt.expected, builder.String()) + assert.Equal(t, tt.args, builder.Args()) + }) + } + + t.Run("panic on invalid operation", func(t *testing.T) { + t.Parallel() + defer func() { + require.NotNil(t, recover()) + }() + builder := &StatementBuilder{} + writeTextOperation(builder, NewColumn("test", "col"), TextOperation(99), "value") + }) +}