Skip to content

Commit

Permalink
add database specific operators
Browse files Browse the repository at this point in the history
  • Loading branch information
FrancoLiberali committed Aug 10, 2023
1 parent 2d85d10 commit fb537b7
Show file tree
Hide file tree
Showing 7 changed files with 289 additions and 34 deletions.
22 changes: 22 additions & 0 deletions orm/mysql/comparison.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package mysql

import (
"github.com/ditrit/badaas/orm/operator"
"github.com/ditrit/badaas/orm/sql"
)

// Pattern Matching

// As an extension to standard SQL, MySQL permits LIKE on numeric expressions.
func Like[T string |
int | int8 | int16 | int32 | int64 |
uint | uint8 | uint16 | uint32 | uint64 |
float32 | float64](pattern string,
) operator.Operator[T] {
return operator.NewValueOperator[T](sql.Like, pattern)
}

// ref: https://dev.mysql.com/doc/refman/8.0/en/regexp.html#operator_regexp
func RegexP(pattern string) operator.Operator[string] {
return operator.NewValueOperator[string](sql.MySQLRegexp, pattern)
}
11 changes: 11 additions & 0 deletions orm/mysql/logical.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package mysql

import (
"github.com/ditrit/badaas/orm/condition"
"github.com/ditrit/badaas/orm/model"
"github.com/ditrit/badaas/orm/sql"
)

func Xor[T model.Model](conditions ...condition.WhereCondition[T]) condition.WhereCondition[T] {
return condition.NewConnectionCondition(sql.MySQLXor, conditions...)
}
27 changes: 27 additions & 0 deletions orm/psql/comparison.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package psql

import (
"github.com/ditrit/badaas/orm/operator"
"github.com/ditrit/badaas/orm/sql"
)

// Pattern Matching

func ILike(pattern string) operator.Operator[string] {
return operator.NewValueOperator[string](sql.PostgreSQLILike, pattern)
}

// ref: https://www.postgresql.org/docs/current/functions-matching.html#FUNCTIONS-SIMILARTO-REGEXP
func SimilarTo(pattern string) operator.Operator[string] {
return operator.NewValueOperator[string](sql.PostgreSQLSimilarTo, pattern)
}

// ref: https://www.postgresql.org/docs/current/functions-matching.html#FUNCTIONS-POSIX-REGEXP
func POSIXMatch(pattern string) operator.Operator[string] {
return operator.NewValueOperator[string](sql.PostgreSQLPosixMatch, pattern)
}

// ref: https://www.postgresql.org/docs/current/functions-matching.html#FUNCTIONS-POSIX-REGEXP
func POSIXIMatch(pattern string) operator.Operator[string] {
return operator.NewValueOperator[string](sql.PostgreSQLPosixIMatch, pattern)
}
92 changes: 58 additions & 34 deletions orm/sql/operators.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,52 +20,76 @@ const (
And
Or
Not
// mysql
MySQLXor
MySQLRegexp
// postgresql
PostgreSQLILike
PostgreSQLSimilarTo
PostgreSQLPosixMatch
PostgreSQLPosixIMatch
// sqlite
SQLiteGlob
)

func (op Operator) String() string {
return operatorToSQL[op]
}

var operatorToSQL = map[Operator]string{
Eq: "=",
NotEq: "<>",
Lt: "<",
LtOrEq: "<=",
Gt: ">",
GtOrEq: ">=",
Between: "BETWEEN",
NotBetween: "NOT BETWEEN",
IsDistinct: "IS DISTINCT FROM",
IsNotDistinct: "IS NOT DISTINCT FROM",
Like: "LIKE",
Escape: "ESCAPE",
ArrayIn: "IN",
ArrayNotIn: "NOT IN",
And: "AND",
Or: "OR",
Not: "NOT",
Eq: "=",
NotEq: "<>",
Lt: "<",
LtOrEq: "<=",
Gt: ">",
GtOrEq: ">=",
Between: "BETWEEN",
NotBetween: "NOT BETWEEN",
IsDistinct: "IS DISTINCT FROM",
IsNotDistinct: "IS NOT DISTINCT FROM",
Like: "LIKE",
Escape: "ESCAPE",
ArrayIn: "IN",
ArrayNotIn: "NOT IN",
And: "AND",
Or: "OR",
Not: "NOT",
MySQLXor: "XOR",
MySQLRegexp: "REGEXP",
PostgreSQLILike: "ILIKE",
PostgreSQLSimilarTo: "SIMILAR TO",
PostgreSQLPosixMatch: "~",
PostgreSQLPosixIMatch: "~*",
SQLiteGlob: "GLOB",
}

func (op Operator) Name() string {
return operatorToName[op]
}

var operatorToName = map[Operator]string{
Eq: "Eq",
NotEq: "NotEq",
Lt: "Lt",
LtOrEq: "LtOrEq",
Gt: "Gt",
GtOrEq: "GtOrEq",
Between: "Between",
NotBetween: "NotBetween",
IsDistinct: "IsDistinct",
IsNotDistinct: "IsNotDistinct",
Like: "Like",
Escape: "Escape",
ArrayIn: "ArrayIn",
ArrayNotIn: "ArrayNotIn",
And: "And",
Or: "Or",
Not: "Not",
Eq: "Eq",
NotEq: "NotEq",
Lt: "Lt",
LtOrEq: "LtOrEq",
Gt: "Gt",
GtOrEq: "GtOrEq",
Between: "Between",
NotBetween: "NotBetween",
IsDistinct: "IsDistinct",
IsNotDistinct: "IsNotDistinct",
Like: "Like",
Escape: "Escape",
ArrayIn: "ArrayIn",
ArrayNotIn: "ArrayNotIn",
And: "And",
Or: "Or",
Not: "Not",
MySQLXor: "mysql.Xor",
MySQLRegexp: "mysql.Regexp",
PostgreSQLILike: "psql.ILike",
PostgreSQLSimilarTo: "psql.SimilarTo",
PostgreSQLPosixMatch: "psql.PosixMatch",
PostgreSQLPosixIMatch: "psql.PosixIMatch",
SQLiteGlob: "sqlite.Glob",
}
11 changes: 11 additions & 0 deletions orm/sqlite/comparison.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package sqlite

import (
"github.com/ditrit/badaas/orm/operator"
"github.com/ditrit/badaas/orm/sql"
)

// ref: https://www.sqlie.org/lang_expr.html#like
func Glob(pattern string) operator.Operator[string] {
return operator.NewValueOperator[string](sql.SQLiteGlob, pattern)
}
134 changes: 134 additions & 0 deletions testintegration/operators_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,16 @@ package testintegration

import (
"database/sql"
"log"
"strings"

"github.com/ditrit/badaas/orm"
"github.com/ditrit/badaas/orm/dynamic"
"github.com/ditrit/badaas/orm/model"
"github.com/ditrit/badaas/orm/mysql"
"github.com/ditrit/badaas/orm/operator"
"github.com/ditrit/badaas/orm/psql"
"github.com/ditrit/badaas/orm/sqlite"
"github.com/ditrit/badaas/orm/unsafe"
"github.com/ditrit/badaas/testintegration/conditions"
"github.com/ditrit/badaas/testintegration/models"
Expand Down Expand Up @@ -536,6 +541,135 @@ func (ts *OperatorsIntTestSuite) TestLikeEscape() {
EqualList(&ts.Suite, []*models.Product{match1, match2}, entities)
}

func (ts *OperatorsIntTestSuite) TestLikeOnNumeric() {
switch getDBDialector() {
case postgreSQL, sqlServer, sqLite:
log.Println("Like with numeric not compatible")
case mySQL:
match1 := ts.createProduct("", 10, 0, false, nil)
match2 := ts.createProduct("", 100, 0, false, nil)

ts.createProduct("", 20, 0, false, nil)
ts.createProduct("", 3, 0, false, nil)

entities, err := ts.crudProductService.Query(
conditions.ProductInt(
mysql.Like[int]("1%"),
),
)
ts.Nil(err)

EqualList(&ts.Suite, []*models.Product{match1, match2}, entities)
}
}

func (ts *OperatorsIntTestSuite) TestILike() {
switch getDBDialector() {
case mySQL, sqlServer, sqLite:
log.Println("ILike not compatible")
case postgreSQL:
match1 := ts.createProduct("basd", 0, 0, false, nil)
match2 := ts.createProduct("cape", 0, 0, false, nil)
match3 := ts.createProduct("bAsd", 0, 0, false, nil)

ts.createProduct("bbsd", 0, 0, false, nil)
ts.createProduct("bbasd", 0, 0, false, nil)

entities, err := ts.crudProductService.Query(
conditions.ProductString(
psql.ILike("_a%"),
),
)
ts.Nil(err)

EqualList(&ts.Suite, []*models.Product{match1, match2, match3}, entities)
}
}

func (ts *OperatorsIntTestSuite) TestSimilarTo() {
switch getDBDialector() {
case mySQL, sqlServer, sqLite:
log.Println("SimilarTo not compatible")
case postgreSQL:
match1 := ts.createProduct("abc", 0, 0, false, nil)
match2 := ts.createProduct("aabcc", 0, 0, false, nil)

ts.createProduct("aec", 0, 0, false, nil)
ts.createProduct("aaaaa", 0, 0, false, nil)

entities, err := ts.crudProductService.Query(
conditions.ProductString(
psql.SimilarTo("%(b|d)%"),
),
)
ts.Nil(err)

EqualList(&ts.Suite, []*models.Product{match1, match2}, entities)
}
}

func (ts *OperatorsIntTestSuite) TestPosixRegexCaseSensitive() {
match1 := ts.createProduct("ab", 0, 0, false, nil)
match2 := ts.createProduct("ax", 0, 0, false, nil)

ts.createProduct("bb", 0, 0, false, nil)
ts.createProduct("cx", 0, 0, false, nil)
ts.createProduct("AB", 0, 0, false, nil)

var posixRegexOperator operator.Operator[string]

switch getDBDialector() {
case sqlServer, mySQL:
log.Println("PosixRegex not compatible")
case postgreSQL:
posixRegexOperator = psql.POSIXMatch("^a(b|x)")
case sqLite:
posixRegexOperator = sqlite.Glob("a[bx]")
}

if posixRegexOperator != nil {
entities, err := ts.crudProductService.Query(
conditions.ProductString(
posixRegexOperator,
),
)
ts.Nil(err)

EqualList(&ts.Suite, []*models.Product{match1, match2}, entities)
}
}

func (ts *OperatorsIntTestSuite) TestPosixRegexCaseInsensitive() {
match1 := ts.createProduct("ab", 0, 0, false, nil)
match2 := ts.createProduct("ax", 0, 0, false, nil)
match3 := ts.createProduct("AB", 0, 0, false, nil)

ts.createProduct("bb", 0, 0, false, nil)
ts.createProduct("cx", 0, 0, false, nil)

var posixRegexOperator operator.Operator[string]

switch getDBDialector() {
case sqlServer, sqLite:
log.Println("PosixRegex Case Insensitive not compatible")
case mySQL:
posixRegexOperator = mysql.RegexP("^a(b|x)")
case postgreSQL:
posixRegexOperator = psql.POSIXIMatch("^a(b|x)")
}

if posixRegexOperator != nil {
entities, err := ts.crudProductService.Query(
conditions.ProductString(
posixRegexOperator,
),
)
ts.Nil(err)

EqualList(&ts.Suite, []*models.Product{match1, match2, match3}, entities)
}
}

func (ts *OperatorsIntTestSuite) TestDynamicOperatorForBasicType() {
int1 := 1
product1 := ts.createProduct("", 1, 0.0, false, &int1)
Expand Down
26 changes: 26 additions & 0 deletions testintegration/where_conditions_test.go
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
package testintegration

import (
"log"

"gorm.io/gorm"
"gotest.tools/assert"

"github.com/ditrit/badaas/orm"
"github.com/ditrit/badaas/orm/errors"
"github.com/ditrit/badaas/orm/model"
"github.com/ditrit/badaas/orm/mysql"
"github.com/ditrit/badaas/orm/unsafe"
"github.com/ditrit/badaas/testintegration/conditions"
"github.com/ditrit/badaas/testintegration/models"
Expand Down Expand Up @@ -521,6 +524,29 @@ func (ts *WhereConditionsIntTestSuite) TestNotOr() {
EqualList(&ts.Suite, []*models.Product{match1, match2, match3}, entities)
}

func (ts *WhereConditionsIntTestSuite) TestXor() {
switch getDBDialector() {
case postgreSQL, sqLite, sqlServer:
log.Println("Xor not compatible")
case mySQL:
match1 := ts.createProduct("", 1, 0, false, nil)
match2 := ts.createProduct("", 7, 0, false, nil)

ts.createProduct("", 5, 0, false, nil)
ts.createProduct("", 4, 0, false, nil)

entities, err := ts.crudProductService.Query(
mysql.Xor(
conditions.ProductInt(orm.Lt(6)),
conditions.ProductInt(orm.Gt(3)),
),
)
ts.Nil(err)

EqualList(&ts.Suite, []*models.Product{match1, match2}, entities)
}
}

func (ts *WhereConditionsIntTestSuite) TestMultipleConditionsDifferentOperators() {
match1 := ts.createProduct("match", 1, 0.0, true, nil)
match2 := ts.createProduct("match", 1, 0.0, true, nil)
Expand Down

0 comments on commit fb537b7

Please sign in to comment.