From bf3ec27f6827cc495e0d122d5fecc32548bb4034 Mon Sep 17 00:00:00 2001 From: go-jet Date: Thu, 15 Aug 2019 11:10:02 +0200 Subject: [PATCH] REGEXP_LIKE refactor. --- internal/jet/bool_expression.go | 4 +- internal/jet/expression.go | 13 ++++-- internal/jet/operators.go | 4 +- internal/jet/string_expression.go | 12 +++-- internal/jet/string_expression_test.go | 9 +++- mysql/dialect.go | 62 ++++++++++++++++++++++++-- mysql/dialect_test.go | 14 ++++++ postgres/dialect.go | 36 ++++++++++++--- postgres/dialect_test.go | 16 ++++--- postgres/functions.go | 1 - tests/mysql/alltypes_test.go | 7 ++- tests/postgres/alltypes_test.go | 9 ++-- 12 files changed, 151 insertions(+), 36 deletions(-) diff --git a/internal/jet/bool_expression.go b/internal/jet/bool_expression.go index f4ca0a6..466f624 100644 --- a/internal/jet/bool_expression.go +++ b/internal/jet/bool_expression.go @@ -92,10 +92,10 @@ type binaryBoolExpression struct { binaryOpExpression } -func newBinaryBoolOperator(lhs, rhs Expression, operator string) BoolExpression { +func newBinaryBoolOperator(lhs, rhs Expression, operator string, additionalParams ...Expression) BoolExpression { binaryBoolExpression := binaryBoolExpression{} - binaryBoolExpression.binaryOpExpression = newBinaryExpression(lhs, rhs, operator) + binaryBoolExpression.binaryOpExpression = newBinaryExpression(lhs, rhs, operator, additionalParams...) binaryBoolExpression.ExpressionInterfaceImpl.Parent = &binaryBoolExpression binaryBoolExpression.boolInterfaceImpl.parent = &binaryBoolExpression diff --git a/internal/jet/expression.go b/internal/jet/expression.go index 817ce6f..d71fa40 100644 --- a/internal/jet/expression.go +++ b/internal/jet/expression.go @@ -77,17 +77,22 @@ func (e *ExpressionInterfaceImpl) serializeForOrderBy(statement StatementType, o // Representation of binary operations (e.g. comparisons, arithmetic) type binaryOpExpression struct { - lhs, rhs Expression - operator string + lhs, rhs Expression + additionalParam Expression + operator string } -func newBinaryExpression(lhs, rhs Expression, operator string) binaryOpExpression { +func newBinaryExpression(lhs, rhs Expression, operator string, additionalParam ...Expression) binaryOpExpression { binaryExpression := binaryOpExpression{ lhs: lhs, rhs: rhs, operator: operator, } + if len(additionalParam) > 0 { + binaryExpression.additionalParam = additionalParam[0] + } + return binaryExpression } @@ -106,7 +111,7 @@ func (c *binaryOpExpression) serialize(statement StatementType, out *SqlBuilder, } if serializeOverride := out.Dialect.SerializeOverride(c.operator); serializeOverride != nil { - serializeOverrideFunc := serializeOverride(c.lhs, c.rhs) + serializeOverrideFunc := serializeOverride(c.lhs, c.rhs, c.additionalParam) serializeOverrideFunc(statement, out, options...) } else { c.lhs.serialize(statement, out) diff --git a/internal/jet/operators.go b/internal/jet/operators.go index ba1f984..74b2be5 100644 --- a/internal/jet/operators.go +++ b/internal/jet/operators.go @@ -1,7 +1,9 @@ package jet const ( - StringConcatOperator = "||" + StringConcatOperator = "||" + StringRegexpLikeOperator = "REGEXP" + StringNotRegexpLikeOperator = "NOT REGEXP" ) //----------- Logical operators ---------------// diff --git a/internal/jet/string_expression.go b/internal/jet/string_expression.go index 1f491b0..ef8d802 100644 --- a/internal/jet/string_expression.go +++ b/internal/jet/string_expression.go @@ -19,7 +19,8 @@ type StringExpression interface { LIKE(pattern StringExpression) BoolExpression NOT_LIKE(pattern StringExpression) BoolExpression - REGEXP_LIKE(pattern StringExpression, matchType ...string) BoolExpression + REGEXP_LIKE(pattern StringExpression, caseSensitive ...bool) BoolExpression + NOT_REGEXP_LIKE(pattern StringExpression, caseSensitive ...bool) BoolExpression } type stringInterfaceImpl struct { @@ -70,11 +71,16 @@ func (s *stringInterfaceImpl) NOT_LIKE(pattern StringExpression) BoolExpression return newBinaryBoolOperator(s.parent, pattern, "NOT LIKE") } -func (s *stringInterfaceImpl) REGEXP_LIKE(pattern StringExpression, matchType ...string) BoolExpression { - return REGEXP_LIKE(s.parent, pattern, matchType...) +func (s *stringInterfaceImpl) REGEXP_LIKE(pattern StringExpression, caseSensitive ...bool) BoolExpression { + return newBinaryBoolOperator(s.parent, pattern, StringRegexpLikeOperator, Bool(len(caseSensitive) > 0 && caseSensitive[0])) +} + +func (s *stringInterfaceImpl) NOT_REGEXP_LIKE(pattern StringExpression, caseSensitive ...bool) BoolExpression { + return newBinaryBoolOperator(s.parent, pattern, StringNotRegexpLikeOperator, Bool(len(caseSensitive) > 0 && caseSensitive[0])) } //---------------------------------------------------// + type binaryStringExpression struct { ExpressionInterfaceImpl stringInterfaceImpl diff --git a/internal/jet/string_expression_test.go b/internal/jet/string_expression_test.go index cd53e51..0f461ac 100644 --- a/internal/jet/string_expression_test.go +++ b/internal/jet/string_expression_test.go @@ -67,8 +67,13 @@ func TestStringNOT_LIKE(t *testing.T) { } func TestStringREGEXP_LIKE(t *testing.T) { - assertClauseSerialize(t, table3StrCol.REGEXP_LIKE(table2ColStr), "REGEXP_LIKE(table3.col2, table2.col_str)") - assertClauseSerialize(t, table3StrCol.REGEXP_LIKE(String("JOHN"), "c"), "REGEXP_LIKE(table3.col2, $1, 'c')", "JOHN") + assertClauseSerialize(t, table3StrCol.REGEXP_LIKE(table2ColStr), "(table3.col2 REGEXP table2.col_str)") + assertClauseSerialize(t, table3StrCol.REGEXP_LIKE(String("JOHN"), true), "(table3.col2 REGEXP $1)", "JOHN") +} + +func TestStringNOT_REGEXP_LIKE(t *testing.T) { + assertClauseSerialize(t, table3StrCol.NOT_REGEXP_LIKE(table2ColStr), "(table3.col2 NOT REGEXP table2.col_str)") + assertClauseSerialize(t, table3StrCol.NOT_REGEXP_LIKE(String("JOHN"), true), "(table3.col2 NOT REGEXP $1)", "JOHN") } func TestStringExp(t *testing.T) { diff --git a/mysql/dialect.go b/mysql/dialect.go index 1da350a..6f8fe31 100644 --- a/mysql/dialect.go +++ b/mysql/dialect.go @@ -9,6 +9,8 @@ var Dialect = NewDialect() func NewDialect() jet.Dialect { serializeOverrides := map[string]jet.SerializeOverride{} + serializeOverrides[jet.StringRegexpLikeOperator] = mysql_REGEXP_LIKE_operator + serializeOverrides[jet.StringNotRegexpLikeOperator] = mysql_NOT_REGEXP_LIKE_operator serializeOverrides["IS DISTINCT FROM"] = mysql_IS_DISTINCT_FROM serializeOverrides["IS NOT DISTINCT FROM"] = mysql_IS_NOT_DISTINCT_FROM serializeOverrides["/"] = mysql_DIVISION @@ -31,7 +33,7 @@ func NewDialect() jet.Dialect { func mysql_BIT_XOR(expressions ...jet.Expression) jet.SerializeFunc { return func(statement jet.StatementType, out *jet.SqlBuilder, options ...jet.SerializeOption) { - if len(expressions) != 2 { + if len(expressions) < 2 { panic("jet: invalid number of expressions for operator XOR") } @@ -48,7 +50,7 @@ func mysql_BIT_XOR(expressions ...jet.Expression) jet.SerializeFunc { func mysql_CONCAT_operator(expressions ...jet.Expression) jet.SerializeFunc { return func(statement jet.StatementType, out *jet.SqlBuilder, options ...jet.SerializeOption) { - if len(expressions) != 2 { + if len(expressions) < 2 { panic("jet: invalid number of expressions for operator CONCAT") } out.WriteString("CONCAT(") @@ -65,7 +67,7 @@ func mysql_CONCAT_operator(expressions ...jet.Expression) jet.SerializeFunc { func mysql_DIVISION(expressions ...jet.Expression) jet.SerializeFunc { return func(statement jet.StatementType, out *jet.SqlBuilder, options ...jet.SerializeOption) { - if len(expressions) != 2 { + if len(expressions) < 2 { panic("jet: invalid number of expressions for operator DIV") } @@ -89,7 +91,7 @@ func mysql_DIVISION(expressions ...jet.Expression) jet.SerializeFunc { func mysql_IS_NOT_DISTINCT_FROM(expressions ...jet.Expression) jet.SerializeFunc { return func(statement jet.StatementType, out *jet.SqlBuilder, options ...jet.SerializeOption) { - if len(expressions) != 2 { + if len(expressions) < 2 { panic("jet: invalid number of expressions for operator") } @@ -106,3 +108,55 @@ func mysql_IS_DISTINCT_FROM(expressions ...jet.Expression) jet.SerializeFunc { out.WriteString(")") } } + +func mysql_REGEXP_LIKE_operator(expressions ...jet.Expression) jet.SerializeFunc { + return func(statement jet.StatementType, out *jet.SqlBuilder, options ...jet.SerializeOption) { + if len(expressions) < 2 { + panic("jet: invalid number of expressions for operator") + } + + jet.Serialize(expressions[0], statement, out, options...) + + caseSensitive := false + + if len(expressions) >= 3 { + if stringLiteral, ok := expressions[2].(jet.LiteralExpression); ok { + caseSensitive = stringLiteral.Value().(bool) + } + } + + out.WriteString("REGEXP") + + if caseSensitive { + out.WriteString("BINARY") + } + + jet.Serialize(expressions[1], statement, out, options...) + } +} + +func mysql_NOT_REGEXP_LIKE_operator(expressions ...jet.Expression) jet.SerializeFunc { + return func(statement jet.StatementType, out *jet.SqlBuilder, options ...jet.SerializeOption) { + if len(expressions) < 2 { + panic("jet: invalid number of expressions for operator") + } + + jet.Serialize(expressions[0], statement, out, options...) + + caseSensitive := false + + if len(expressions) >= 3 { + if stringLiteral, ok := expressions[2].(jet.LiteralExpression); ok { + caseSensitive = stringLiteral.Value().(bool) + } + } + + out.WriteString("NOT REGEXP") + + if caseSensitive { + out.WriteString("BINARY") + } + + jet.Serialize(expressions[1], statement, out, options...) + } +} diff --git a/mysql/dialect_test.go b/mysql/dialect_test.go index ed202d8..936277a 100644 --- a/mysql/dialect_test.go +++ b/mysql/dialect_test.go @@ -46,3 +46,17 @@ func TestExists(t *testing.T) { WHERE table1.col1 = table2.col3 ))`, int64(1)) } + +func TestString_REGEXP_LIKE_operator(t *testing.T) { + assertClauseSerialize(t, table3StrCol.REGEXP_LIKE(table2ColStr), "(table3.col2 REGEXP table2.col_str)") + assertClauseSerialize(t, table3StrCol.REGEXP_LIKE(String("JOHN")), "(table3.col2 REGEXP ?)", "JOHN") + assertClauseSerialize(t, table3StrCol.REGEXP_LIKE(String("JOHN"), false), "(table3.col2 REGEXP ?)", "JOHN") + assertClauseSerialize(t, table3StrCol.REGEXP_LIKE(String("JOHN"), true), "(table3.col2 REGEXP BINARY ?)", "JOHN") +} + +func TestString_NOT_REGEXP_LIKE_operator(t *testing.T) { + assertClauseSerialize(t, table3StrCol.NOT_REGEXP_LIKE(table2ColStr), "(table3.col2 NOT REGEXP table2.col_str)") + assertClauseSerialize(t, table3StrCol.NOT_REGEXP_LIKE(String("JOHN")), "(table3.col2 NOT REGEXP ?)", "JOHN") + assertClauseSerialize(t, table3StrCol.NOT_REGEXP_LIKE(String("JOHN"), false), "(table3.col2 NOT REGEXP ?)", "JOHN") + assertClauseSerialize(t, table3StrCol.NOT_REGEXP_LIKE(String("JOHN"), true), "(table3.col2 NOT REGEXP BINARY ?)", "JOHN") +} diff --git a/postgres/dialect.go b/postgres/dialect.go index 27656b4..15ed7e0 100644 --- a/postgres/dialect.go +++ b/postgres/dialect.go @@ -3,7 +3,6 @@ package postgres import ( "github.com/go-jet/jet/internal/jet" "strconv" - "strings" ) var Dialect = NewDialect() @@ -11,7 +10,8 @@ var Dialect = NewDialect() func NewDialect() jet.Dialect { serializeOverrides := map[string]jet.SerializeOverride{} - serializeOverrides["REGEXP_LIKE"] = postgres_REGEXP_LIKE_function + serializeOverrides[jet.StringRegexpLikeOperator] = postgres_REGEXP_LIKE_operator + serializeOverrides[jet.StringNotRegexpLikeOperator] = postgres_NOT_REGEXP_LIKE_operator serializeOverrides["CAST"] = postgresCAST dialectParams := jet.DialectParams{ @@ -53,7 +53,7 @@ func postgresCAST(expressions ...jet.Expression) jet.SerializeFunc { } } -func postgres_REGEXP_LIKE_function(expressions ...jet.Expression) jet.SerializeFunc { +func postgres_REGEXP_LIKE_operator(expressions ...jet.Expression) jet.SerializeFunc { return func(statement jet.StatementType, out *jet.SqlBuilder, options ...jet.SerializeOption) { if len(expressions) < 2 { panic("jet: invalid number of expressions for operator") @@ -65,9 +65,7 @@ func postgres_REGEXP_LIKE_function(expressions ...jet.Expression) jet.SerializeF if len(expressions) >= 3 { if stringLiteral, ok := expressions[2].(jet.LiteralExpression); ok { - matchType := stringLiteral.Value().(string) - - caseSensitive = !strings.Contains(matchType, "i") + caseSensitive = stringLiteral.Value().(bool) } } @@ -80,3 +78,29 @@ func postgres_REGEXP_LIKE_function(expressions ...jet.Expression) jet.SerializeF jet.Serialize(expressions[1], statement, out, options...) } } + +func postgres_NOT_REGEXP_LIKE_operator(expressions ...jet.Expression) jet.SerializeFunc { + return func(statement jet.StatementType, out *jet.SqlBuilder, options ...jet.SerializeOption) { + if len(expressions) < 2 { + panic("jet: invalid number of expressions for operator") + } + + jet.Serialize(expressions[0], statement, out, options...) + + caseSensitive := false + + if len(expressions) >= 3 { + if stringLiteral, ok := expressions[2].(jet.LiteralExpression); ok { + caseSensitive = stringLiteral.Value().(bool) + } + } + + if caseSensitive { + out.WriteString("!~") + } else { + out.WriteString("!~*") + } + + jet.Serialize(expressions[1], statement, out, options...) + } +} diff --git a/postgres/dialect_test.go b/postgres/dialect_test.go index aecb97f..b6061b7 100644 --- a/postgres/dialect_test.go +++ b/postgres/dialect_test.go @@ -3,15 +3,17 @@ package postgres import "testing" func TestString_REGEXP_LIKE_operator(t *testing.T) { - assertClauseSerialize(t, table3StrCol.REGEXP_LIKE(table2ColStr), "table3.col2 ~* table2.col_str") - assertClauseSerialize(t, table3StrCol.REGEXP_LIKE(String("JOHN"), "c"), "table3.col2 ~ $1", "JOHN") - assertClauseSerialize(t, table3StrCol.REGEXP_LIKE(String("JOHN"), "i"), "table3.col2 ~* $1", "JOHN") + assertClauseSerialize(t, table3StrCol.REGEXP_LIKE(table2ColStr), "(table3.col2 ~* table2.col_str)") + assertClauseSerialize(t, table3StrCol.REGEXP_LIKE(String("JOHN")), "(table3.col2 ~* $1)", "JOHN") + assertClauseSerialize(t, table3StrCol.REGEXP_LIKE(String("JOHN"), false), "(table3.col2 ~* $1)", "JOHN") + assertClauseSerialize(t, table3StrCol.REGEXP_LIKE(String("JOHN"), true), "(table3.col2 ~ $1)", "JOHN") } -func TestString_REGEXP_LIKE_function(t *testing.T) { - assertClauseSerialize(t, REGEXP_LIKE(table3StrCol, table2ColStr), "table3.col2 ~* table2.col_str") - assertClauseSerialize(t, REGEXP_LIKE(table3StrCol, String("JOHN"), "c"), "table3.col2 ~ $1", "JOHN") - assertClauseSerialize(t, REGEXP_LIKE(table3StrCol, String("JOHN"), "i"), "table3.col2 ~* $1", "JOHN") +func TestString_NOT_REGEXP_LIKE_operator(t *testing.T) { + assertClauseSerialize(t, table3StrCol.NOT_REGEXP_LIKE(table2ColStr), "(table3.col2 !~* table2.col_str)") + assertClauseSerialize(t, table3StrCol.NOT_REGEXP_LIKE(String("JOHN")), "(table3.col2 !~* $1)", "JOHN") + assertClauseSerialize(t, table3StrCol.NOT_REGEXP_LIKE(String("JOHN"), false), "(table3.col2 !~* $1)", "JOHN") + assertClauseSerialize(t, table3StrCol.NOT_REGEXP_LIKE(String("JOHN"), true), "(table3.col2 !~ $1)", "JOHN") } func TestExists(t *testing.T) { diff --git a/postgres/functions.go b/postgres/functions.go index 8f273aa..fafb16a 100644 --- a/postgres/functions.go +++ b/postgres/functions.go @@ -38,7 +38,6 @@ var SUMi = jet.SUMi //--------------------- String functions ------------------// -var REGEXP_LIKE = jet.REGEXP_LIKE var BIT_LENGTH = jet.BIT_LENGTH var CHAR_LENGTH = jet.CHAR_LENGTH var OCTET_LENGTH = jet.OCTET_LENGTH diff --git a/tests/mysql/alltypes_test.go b/tests/mysql/alltypes_test.go index 5c16465..7adef82 100644 --- a/tests/mysql/alltypes_test.go +++ b/tests/mysql/alltypes_test.go @@ -445,8 +445,11 @@ func TestStringOperators(t *testing.T) { AllTypes.Text.LIKE(String("abc")), AllTypes.Text.NOT_LIKE(String("_b_")), AllTypes.Text.REGEXP_LIKE(String("aba")), - AllTypes.Text.REGEXP_LIKE(String("aba"), "c"), - String("ABA").REGEXP_LIKE(String("aba"), "i"), + AllTypes.Text.REGEXP_LIKE(String("aba"), false), + String("ABA").REGEXP_LIKE(String("aba"), true), + AllTypes.Text.NOT_REGEXP_LIKE(String("aba")), + AllTypes.Text.NOT_REGEXP_LIKE(String("aba"), false), + String("ABA").NOT_REGEXP_LIKE(String("aba"), true), BIT_LENGTH(AllTypes.Text), CHAR_LENGTH(AllTypes.Char), diff --git a/tests/postgres/alltypes_test.go b/tests/postgres/alltypes_test.go index 44b5b46..c925d66 100644 --- a/tests/postgres/alltypes_test.go +++ b/tests/postgres/alltypes_test.go @@ -179,9 +179,10 @@ func TestStringOperators(t *testing.T) { AllTypes.Text.CONCAT(Int(11)), AllTypes.Text.LIKE(String("abc")), AllTypes.Text.NOT_LIKE(String("_b_")), - AllTypes.Text.REGEXP_LIKE(String("aba")), - AllTypes.Text.REGEXP_LIKE(String("aba"), "c"), - AllTypes.Text.REGEXP_LIKE(String("aba"), "i"), + AllTypes.Text.REGEXP_LIKE(String("^t")), + AllTypes.Text.REGEXP_LIKE(String("^t"), true), + AllTypes.Text.NOT_REGEXP_LIKE(String("^t")), + AllTypes.Text.NOT_REGEXP_LIKE(String("^t"), true), BIT_LENGTH(String("length")), CHAR_LENGTH(AllTypes.Char), @@ -232,7 +233,7 @@ func TestStringOperators(t *testing.T) { TO_HEX(AllTypes.IntegerPtr), ) - //fmt.Println(query.Sql()) + //fmt.Println(query.DebugSql()) err := query.Query(db, &struct{}{})