diff --git a/dialect/mysql/mysql.go b/dialect/mysql/mysql.go index 2a8934a2..d5566e49 100644 --- a/dialect/mysql/mysql.go +++ b/dialect/mysql/mysql.go @@ -51,6 +51,14 @@ func DialectOptions() *goqu.SQLDialectOptions { exp.RegexpILikeOp: []byte("REGEXP"), exp.RegexpNotILikeOp: []byte("NOT REGEXP"), } + opts.BitwiseOperatorLookup = map[exp.BitwiseOperation][]byte{ + exp.BitwiseInversionOp: []byte("~"), + exp.BitwiseOrOp: []byte("|"), + exp.BitwiseAndOp: []byte("&"), + exp.BitwiseXorOp: []byte("^"), + exp.BitwiseLeftShiftOp: []byte("<<"), + exp.BitwiseRightShiftOp: []byte(">>"), + } opts.EscapedRunes = map[rune][]byte{ '\'': []byte("\\'"), '"': []byte("\\\""), diff --git a/dialect/mysql/mysql_dialect_test.go b/dialect/mysql/mysql_dialect_test.go index 397a0845..b57184d4 100644 --- a/dialect/mysql/mysql_dialect_test.go +++ b/dialect/mysql/mysql_dialect_test.go @@ -112,6 +112,19 @@ func (mds *mysqlDialectSuite) TestBooleanOperations() { ) } +func (mds *mysqlDialectSuite) TestBitwiseOperations() { + col := goqu.C("a") + ds := mds.GetDs("test") + mds.assertSQL( + sqlTestCase{ds: ds.Where(col.BitwiseInversion()), sql: "SELECT * FROM `test` WHERE (~ `a`)"}, + sqlTestCase{ds: ds.Where(col.BitwiseAnd(1)), sql: "SELECT * FROM `test` WHERE (`a` & 1)"}, + sqlTestCase{ds: ds.Where(col.BitwiseOr(1)), sql: "SELECT * FROM `test` WHERE (`a` | 1)"}, + sqlTestCase{ds: ds.Where(col.BitwiseXor(1)), sql: "SELECT * FROM `test` WHERE (`a` ^ 1)"}, + sqlTestCase{ds: ds.Where(col.BitwiseLeftShift(1)), sql: "SELECT * FROM `test` WHERE (`a` << 1)"}, + sqlTestCase{ds: ds.Where(col.BitwiseRightShift(1)), sql: "SELECT * FROM `test` WHERE (`a` >> 1)"}, + ) +} + func (mds *mysqlDialectSuite) TestUpdateSQL() { ds := mds.GetDs("test").Update() mds.assertSQL( diff --git a/dialect/sqlite3/sqlite3.go b/dialect/sqlite3/sqlite3.go index e70222fb..08df9284 100644 --- a/dialect/sqlite3/sqlite3.go +++ b/dialect/sqlite3/sqlite3.go @@ -52,6 +52,12 @@ func DialectOptions() *goqu.SQLDialectOptions { exp.RegexpNotILikeOp: []byte("NOT REGEXP"), } opts.UseLiteralIsBools = false + opts.BitwiseOperatorLookup = map[exp.BitwiseOperation][]byte{ + exp.BitwiseOrOp: []byte("|"), + exp.BitwiseAndOp: []byte("&"), + exp.BitwiseLeftShiftOp: []byte("<<"), + exp.BitwiseRightShiftOp: []byte(">>"), + } opts.EscapedRunes = map[rune][]byte{ '\'': []byte("''"), } diff --git a/dialect/sqlite3/sqlite3_dialect_test.go b/dialect/sqlite3/sqlite3_dialect_test.go index 6a83b5f1..8aa291d2 100644 --- a/dialect/sqlite3/sqlite3_dialect_test.go +++ b/dialect/sqlite3/sqlite3_dialect_test.go @@ -132,6 +132,19 @@ func (sds *sqlite3DialectSuite) TestBooleanOperations() { ) } +func (sds *sqlite3DialectSuite) TestBitwiseOperations() { + col := goqu.C("a") + ds := sds.GetDs("test") + sds.assertSQL( + sqlTestCase{ds: ds.Where(col.BitwiseInversion()), err: "goqu: bitwise operator 'Inversion' not supported"}, + sqlTestCase{ds: ds.Where(col.BitwiseAnd(1)), sql: "SELECT * FROM `test` WHERE (`a` & 1)"}, + sqlTestCase{ds: ds.Where(col.BitwiseOr(1)), sql: "SELECT * FROM `test` WHERE (`a` | 1)"}, + sqlTestCase{ds: ds.Where(col.BitwiseXor(1)), err: "goqu: bitwise operator 'XOR' not supported"}, + sqlTestCase{ds: ds.Where(col.BitwiseLeftShift(1)), sql: "SELECT * FROM `test` WHERE (`a` << 1)"}, + sqlTestCase{ds: ds.Where(col.BitwiseRightShift(1)), sql: "SELECT * FROM `test` WHERE (`a` >> 1)"}, + ) +} + func (sds *sqlite3DialectSuite) TestForUpdate() { ds := sds.GetDs("test") sds.assertSQL( diff --git a/dialect/sqlserver/sqlserver.go b/dialect/sqlserver/sqlserver.go index 58f9ad22..9cb20d65 100644 --- a/dialect/sqlserver/sqlserver.go +++ b/dialect/sqlserver/sqlserver.go @@ -53,6 +53,12 @@ func DialectOptions() *goqu.SQLDialectOptions { exp.RegexpILikeOp: []byte("REGEXP"), exp.RegexpNotILikeOp: []byte("NOT REGEXP"), } + opts.BitwiseOperatorLookup = map[exp.BitwiseOperation][]byte{ + exp.BitwiseInversionOp: []byte("~"), + exp.BitwiseOrOp: []byte("|"), + exp.BitwiseAndOp: []byte("&"), + exp.BitwiseXorOp: []byte("^"), + } opts.FetchFragment = []byte(" FETCH FIRST ") diff --git a/dialect/sqlserver/sqlserver_dialect_test.go b/dialect/sqlserver/sqlserver_dialect_test.go new file mode 100644 index 00000000..1a150c36 --- /dev/null +++ b/dialect/sqlserver/sqlserver_dialect_test.go @@ -0,0 +1,60 @@ +package sqlserver_test + +import ( + "testing" + + "github.com/doug-martin/goqu/v9" + "github.com/doug-martin/goqu/v9/exp" + "github.com/stretchr/testify/suite" +) + +type ( + sqlserverDialectSuite struct { + suite.Suite + } + sqlTestCase struct { + ds exp.SQLExpression + sql string + err string + isPrepared bool + args []interface{} + } +) + +func (sds *sqlserverDialectSuite) GetDs(table string) *goqu.SelectDataset { + return goqu.Dialect("sqlserver").From(table) +} + +func (sds *sqlserverDialectSuite) assertSQL(cases ...sqlTestCase) { + for i, c := range cases { + actualSQL, actualArgs, err := c.ds.ToSQL() + if c.err == "" { + sds.NoError(err, "test case %d failed", i) + } else { + sds.EqualError(err, c.err, "test case %d failed", i) + } + sds.Equal(c.sql, actualSQL, "test case %d failed", i) + if c.isPrepared && c.args != nil || len(c.args) > 0 { + sds.Equal(c.args, actualArgs, "test case %d failed", i) + } else { + sds.Empty(actualArgs, "test case %d failed", i) + } + } +} + +func (sds *sqlserverDialectSuite) TestBitwiseOperations() { + col := goqu.C("a") + ds := sds.GetDs("test") + sds.assertSQL( + sqlTestCase{ds: ds.Where(col.BitwiseInversion()), sql: "SELECT * FROM \"test\" WHERE (~ \"a\")"}, + sqlTestCase{ds: ds.Where(col.BitwiseAnd(1)), sql: "SELECT * FROM \"test\" WHERE (\"a\" & 1)"}, + sqlTestCase{ds: ds.Where(col.BitwiseOr(1)), sql: "SELECT * FROM \"test\" WHERE (\"a\" | 1)"}, + sqlTestCase{ds: ds.Where(col.BitwiseXor(1)), sql: "SELECT * FROM \"test\" WHERE (\"a\" ^ 1)"}, + sqlTestCase{ds: ds.Where(col.BitwiseLeftShift(1)), err: "goqu: bitwise operator 'Left Shift' not supported"}, + sqlTestCase{ds: ds.Where(col.BitwiseRightShift(1)), err: "goqu: bitwise operator 'Right Shift' not supported"}, + ) +} + +func TestDatasetAdapterSuite(t *testing.T) { + suite.Run(t, new(sqlserverDialectSuite)) +} diff --git a/exp/bitwise.go b/exp/bitwise.go new file mode 100644 index 00000000..eede5a2f --- /dev/null +++ b/exp/bitwise.go @@ -0,0 +1,89 @@ +package exp + +type bitwise struct { + lhs Expression + rhs interface{} + op BitwiseOperation +} + +func NewBitwiseExpression(op BitwiseOperation, lhs Expression, rhs interface{}) BitwiseExpression { + return bitwise{op: op, lhs: lhs, rhs: rhs} +} + +func (b bitwise) Clone() Expression { + return NewBitwiseExpression(b.op, b.lhs.Clone(), b.rhs) +} + +func (b bitwise) RHS() interface{} { + return b.rhs +} + +func (b bitwise) LHS() Expression { + return b.lhs +} + +func (b bitwise) Op() BitwiseOperation { + return b.op +} + +func (b bitwise) Expression() Expression { return b } +func (b bitwise) As(val interface{}) AliasedExpression { return NewAliasExpression(b, val) } +func (b bitwise) Eq(val interface{}) BooleanExpression { return eq(b, val) } +func (b bitwise) Neq(val interface{}) BooleanExpression { return neq(b, val) } +func (b bitwise) Gt(val interface{}) BooleanExpression { return gt(b, val) } +func (b bitwise) Gte(val interface{}) BooleanExpression { return gte(b, val) } +func (b bitwise) Lt(val interface{}) BooleanExpression { return lt(b, val) } +func (b bitwise) Lte(val interface{}) BooleanExpression { return lte(b, val) } +func (b bitwise) Asc() OrderedExpression { return asc(b) } +func (b bitwise) Desc() OrderedExpression { return desc(b) } +func (b bitwise) Like(i interface{}) BooleanExpression { return like(b, i) } +func (b bitwise) NotLike(i interface{}) BooleanExpression { return notLike(b, i) } +func (b bitwise) ILike(i interface{}) BooleanExpression { return iLike(b, i) } +func (b bitwise) NotILike(i interface{}) BooleanExpression { return notILike(b, i) } +func (b bitwise) RegexpLike(val interface{}) BooleanExpression { return regexpLike(b, val) } +func (b bitwise) RegexpNotLike(val interface{}) BooleanExpression { return regexpNotLike(b, val) } +func (b bitwise) RegexpILike(val interface{}) BooleanExpression { return regexpILike(b, val) } +func (b bitwise) RegexpNotILike(val interface{}) BooleanExpression { return regexpNotILike(b, val) } +func (b bitwise) In(i ...interface{}) BooleanExpression { return in(b, i...) } +func (b bitwise) NotIn(i ...interface{}) BooleanExpression { return notIn(b, i...) } +func (b bitwise) Is(i interface{}) BooleanExpression { return is(b, i) } +func (b bitwise) IsNot(i interface{}) BooleanExpression { return isNot(b, i) } +func (b bitwise) IsNull() BooleanExpression { return is(b, nil) } +func (b bitwise) IsNotNull() BooleanExpression { return isNot(b, nil) } +func (b bitwise) IsTrue() BooleanExpression { return is(b, true) } +func (b bitwise) IsNotTrue() BooleanExpression { return isNot(b, true) } +func (b bitwise) IsFalse() BooleanExpression { return is(b, false) } +func (b bitwise) IsNotFalse() BooleanExpression { return isNot(b, false) } +func (b bitwise) Distinct() SQLFunctionExpression { return NewSQLFunctionExpression("DISTINCT", b) } +func (b bitwise) Between(val RangeVal) RangeExpression { return between(b, val) } +func (b bitwise) NotBetween(val RangeVal) RangeExpression { return notBetween(b, val) } + +// used internally to create a Bitwise Inversion BitwiseExpression +func bitwiseInversion(rhs Expression) BitwiseExpression { + return NewBitwiseExpression(BitwiseInversionOp, nil, rhs) +} + +// used internally to create a Bitwise OR BitwiseExpression +func bitwiseOr(lhs Expression, rhs interface{}) BitwiseExpression { + return NewBitwiseExpression(BitwiseOrOp, lhs, rhs) +} + +// used internally to create a Bitwise AND BitwiseExpression +func bitwiseAnd(lhs Expression, rhs interface{}) BitwiseExpression { + return NewBitwiseExpression(BitwiseAndOp, lhs, rhs) +} + +// used internally to create a Bitwise XOR BitwiseExpression +func bitwiseXor(lhs Expression, rhs interface{}) BitwiseExpression { + return NewBitwiseExpression(BitwiseXorOp, lhs, rhs) +} + +// used internally to create a Bitwise LEFT SHIFT BitwiseExpression +func bitwiseLeftShift(lhs Expression, rhs interface{}) BitwiseExpression { + return NewBitwiseExpression(BitwiseLeftShiftOp, lhs, rhs) +} + +// used internally to create a Bitwise RIGHT SHIFT BitwiseExpression +func bitwiseRightShift(lhs Expression, rhs interface{}) BitwiseExpression { + return NewBitwiseExpression(BitwiseRightShiftOp, lhs, rhs) +} diff --git a/exp/bitwise_test.go b/exp/bitwise_test.go new file mode 100644 index 00000000..5dcbd86f --- /dev/null +++ b/exp/bitwise_test.go @@ -0,0 +1,84 @@ +package exp_test + +import ( + "testing" + + "github.com/doug-martin/goqu/v9/exp" + "github.com/stretchr/testify/suite" +) + +type bitwiseExpressionSuite struct { + suite.Suite +} + +func TestBitwiseExpressionSuite(t *testing.T) { + suite.Run(t, &bitwiseExpressionSuite{}) +} + +func (bes *bitwiseExpressionSuite) TestClone() { + be := exp.NewBitwiseExpression(exp.BitwiseAndOp, exp.NewIdentifierExpression("", "", "col"), 1) + bes.Equal(be, be.Clone()) +} + +func (bes *bitwiseExpressionSuite) TestExpression() { + be := exp.NewBitwiseExpression(exp.BitwiseAndOp, exp.NewIdentifierExpression("", "", "col"), 1) + bes.Equal(be, be.Expression()) +} + +func (bes *bitwiseExpressionSuite) TestAs() { + be := exp.NewBitwiseExpression(exp.BitwiseInversionOp, exp.NewIdentifierExpression("", "", "col"), 1) + bes.Equal(exp.NewAliasExpression(be, "a"), be.As("a")) +} + +func (bes *bitwiseExpressionSuite) TestAsc() { + be := exp.NewBitwiseExpression(exp.BitwiseAndOp, exp.NewIdentifierExpression("", "", "col"), 1) + bes.Equal(exp.NewOrderedExpression(be, exp.AscDir, exp.NoNullsSortType), be.Asc()) +} + +func (bes *bitwiseExpressionSuite) TestDesc() { + be := exp.NewBitwiseExpression(exp.BitwiseOrOp, exp.NewIdentifierExpression("", "", "col"), 1) + bes.Equal(exp.NewOrderedExpression(be, exp.DescSortDir, exp.NoNullsSortType), be.Desc()) +} + +func (bes *bitwiseExpressionSuite) TestAllOthers() { + be := exp.NewBitwiseExpression(exp.BitwiseRightShiftOp, exp.NewIdentifierExpression("", "", "col"), 1) + rv := exp.NewRangeVal(1, 2) + pattern := "cast like%" + inVals := []interface{}{1, 2} + testCases := []struct { + Ex exp.Expression + Expected exp.Expression + }{ + {Ex: be.Eq(1), Expected: exp.NewBooleanExpression(exp.EqOp, be, 1)}, + {Ex: be.Neq(1), Expected: exp.NewBooleanExpression(exp.NeqOp, be, 1)}, + {Ex: be.Gt(1), Expected: exp.NewBooleanExpression(exp.GtOp, be, 1)}, + {Ex: be.Gte(1), Expected: exp.NewBooleanExpression(exp.GteOp, be, 1)}, + {Ex: be.Lt(1), Expected: exp.NewBooleanExpression(exp.LtOp, be, 1)}, + {Ex: be.Lte(1), Expected: exp.NewBooleanExpression(exp.LteOp, be, 1)}, + {Ex: be.Between(rv), Expected: exp.NewRangeExpression(exp.BetweenOp, be, rv)}, + {Ex: be.NotBetween(rv), Expected: exp.NewRangeExpression(exp.NotBetweenOp, be, rv)}, + {Ex: be.Like(pattern), Expected: exp.NewBooleanExpression(exp.LikeOp, be, pattern)}, + {Ex: be.NotLike(pattern), Expected: exp.NewBooleanExpression(exp.NotLikeOp, be, pattern)}, + {Ex: be.ILike(pattern), Expected: exp.NewBooleanExpression(exp.ILikeOp, be, pattern)}, + {Ex: be.NotILike(pattern), Expected: exp.NewBooleanExpression(exp.NotILikeOp, be, pattern)}, + {Ex: be.RegexpLike(pattern), Expected: exp.NewBooleanExpression(exp.RegexpLikeOp, be, pattern)}, + {Ex: be.RegexpNotLike(pattern), Expected: exp.NewBooleanExpression(exp.RegexpNotLikeOp, be, pattern)}, + {Ex: be.RegexpILike(pattern), Expected: exp.NewBooleanExpression(exp.RegexpILikeOp, be, pattern)}, + {Ex: be.RegexpNotILike(pattern), Expected: exp.NewBooleanExpression(exp.RegexpNotILikeOp, be, pattern)}, + {Ex: be.In(inVals), Expected: exp.NewBooleanExpression(exp.InOp, be, inVals)}, + {Ex: be.NotIn(inVals), Expected: exp.NewBooleanExpression(exp.NotInOp, be, inVals)}, + {Ex: be.Is(true), Expected: exp.NewBooleanExpression(exp.IsOp, be, true)}, + {Ex: be.IsNot(true), Expected: exp.NewBooleanExpression(exp.IsNotOp, be, true)}, + {Ex: be.IsNull(), Expected: exp.NewBooleanExpression(exp.IsOp, be, nil)}, + {Ex: be.IsNotNull(), Expected: exp.NewBooleanExpression(exp.IsNotOp, be, nil)}, + {Ex: be.IsTrue(), Expected: exp.NewBooleanExpression(exp.IsOp, be, true)}, + {Ex: be.IsNotTrue(), Expected: exp.NewBooleanExpression(exp.IsNotOp, be, true)}, + {Ex: be.IsFalse(), Expected: exp.NewBooleanExpression(exp.IsOp, be, false)}, + {Ex: be.IsNotFalse(), Expected: exp.NewBooleanExpression(exp.IsNotOp, be, false)}, + {Ex: be.Distinct(), Expected: exp.NewSQLFunctionExpression("DISTINCT", be)}, + } + + for _, tc := range testCases { + bes.Equal(tc.Expected, tc.Ex) + } +} diff --git a/exp/exp.go b/exp/exp.go index 50e5473b..ec33c143 100644 --- a/exp/exp.go +++ b/exp/exp.go @@ -138,6 +138,27 @@ type ( // Used internally by update sql Set(interface{}) UpdateExpression } + + Bitwiseable interface { + // Creates a Bit Operation Expresion for sql ~ + // I("col").BitiInversion() // (~ "col") + BitwiseInversion() BitwiseExpression + // Creates a Bit Operation Expresion for sql | + // I("col").BitOr(1) // ("col" | 1) + BitwiseOr(interface{}) BitwiseExpression + // Creates a Bit Operation Expresion for sql & + // I("col").BitAnd(1) // ("col" & 1) + BitwiseAnd(interface{}) BitwiseExpression + // Creates a Bit Operation Expresion for sql ^ + // I("col").BitXor(1) // ("col" ^ 1) + BitwiseXor(interface{}) BitwiseExpression + // Creates a Bit Operation Expresion for sql << + // I("col").BitLeftShift(1) // ("col" << 1) + BitwiseLeftShift(interface{}) BitwiseExpression + // Creates a Bit Operation Expresion for sql >> + // I("col").BitRighttShift(1) // ("col" >> 1) + BitwiseRightShift(interface{}) BitwiseExpression + } ) type ( @@ -195,6 +216,26 @@ type ( // The right hand side of the expression could be a primitive value, dataset, or expression RHS() interface{} } + + BitwiseOperation int + BitwiseExpression interface { + Expression + Aliaseable + Comparable + Isable + Inable + Likeable + Rangeable + Orderable + Distinctable + // Returns the operator for the expression + Op() BitwiseOperation + // The left hand side of the expression (e.g. I("a") + LHS() Expression + // The right hand side of the expression could be a primitive value, dataset, or expression + RHS() interface{} + } + // An Expression that represents another Expression casted to a SQL type CastExpression interface { Expression @@ -276,6 +317,7 @@ type ( Updateable Distinctable Castable + Bitwiseable // returns true if this identifier has more more than on part (Schema, Table or Col) // "schema" -> true //cant qualify anymore // "schema.table" -> true @@ -345,6 +387,7 @@ type ( Likeable Rangeable Orderable + Bitwiseable // Returns the literal sql Literal() string // Arguments to be replaced within the sql @@ -547,6 +590,13 @@ const ( RegexpNotILikeOp betweenStr = "between" + + BitwiseInversionOp BitwiseOperation = iota + BitwiseOrOp + BitwiseAndOp + BitwiseXorOp + BitwiseLeftShiftOp + BitwiseRightShiftOp ) var ( @@ -624,6 +674,24 @@ func (bo BooleanOperation) String() string { return fmt.Sprintf("%d", bo) } +func (bi BitwiseOperation) String() string { + switch bi { + case BitwiseInversionOp: + return "Inversion" + case BitwiseOrOp: + return "OR" + case BitwiseAndOp: + return "AND" + case BitwiseXorOp: + return "XOR" + case BitwiseLeftShiftOp: + return "Left Shift" + case BitwiseRightShiftOp: + return "Right Shift" + } + return fmt.Sprintf("%d", bi) +} + func (ro RangeOperation) String() string { switch ro { case BetweenOp: diff --git a/exp/ident.go b/exp/ident.go index 1cb9d1c4..aebbbd5a 100644 --- a/exp/ident.go +++ b/exp/ident.go @@ -160,6 +160,28 @@ func (i identifier) Lt(val interface{}) BooleanExpression { return lt(i, val) } // (e.g "my_col" <= 1) func (i identifier) Lte(val interface{}) BooleanExpression { return lte(i, val) } +// Returns a BooleanExpression for bit inversion (e.g ~ "my_col") +func (i identifier) BitwiseInversion() BitwiseExpression { return bitwiseInversion(i) } + +// Returns a BooleanExpression for bit OR (e.g "my_col" | 1) +func (i identifier) BitwiseOr(val interface{}) BitwiseExpression { return bitwiseOr(i, val) } + +// Returns a BooleanExpression for bit AND (e.g "my_col" & 1) +func (i identifier) BitwiseAnd(val interface{}) BitwiseExpression { return bitwiseAnd(i, val) } + +// Returns a BooleanExpression for bit XOR (e.g "my_col" ^ 1) +func (i identifier) BitwiseXor(val interface{}) BitwiseExpression { return bitwiseXor(i, val) } + +// Returns a BooleanExpression for bit LEFT shift (e.g "my_col" << 1) +func (i identifier) BitwiseLeftShift(val interface{}) BitwiseExpression { + return bitwiseLeftShift(i, val) +} + +// Returns a BooleanExpression for bit RIGHT shift (e.g "my_col" >> 1) +func (i identifier) BitwiseRightShift(val interface{}) BitwiseExpression { + return bitwiseRightShift(i, val) +} + // Returns a BooleanExpression for checking that a identifier is in a list of values or (e.g "my_col" > 1) func (i identifier) In(vals ...interface{}) BooleanExpression { return in(i, vals...) } func (i identifier) NotIn(vals ...interface{}) BooleanExpression { return notIn(i, vals...) } diff --git a/exp/ident_test.go b/exp/ident_test.go index 3e2d7ac2..d74d83ed 100644 --- a/exp/ident_test.go +++ b/exp/ident_test.go @@ -198,6 +198,7 @@ func (ies *identifierExpressionSuite) TestAllOthers() { rv := exp.NewRangeVal(1, 2) pattern := "ident like%" inVals := []interface{}{1, 2} + bitwiseVals := 2 testCases := []struct { Ex exp.Expression Expected exp.Expression @@ -232,6 +233,12 @@ func (ies *identifierExpressionSuite) TestAllOthers() { {Ex: ident.IsFalse(), Expected: exp.NewBooleanExpression(exp.IsOp, ident, false)}, {Ex: ident.IsNotFalse(), Expected: exp.NewBooleanExpression(exp.IsNotOp, ident, false)}, {Ex: ident.Distinct(), Expected: exp.NewSQLFunctionExpression("DISTINCT", ident)}, + {Ex: ident.BitwiseInversion(), Expected: exp.NewBitwiseExpression(exp.BitwiseInversionOp, nil, ident)}, + {Ex: ident.BitwiseOr(bitwiseVals), Expected: exp.NewBitwiseExpression(exp.BitwiseOrOp, ident, bitwiseVals)}, + {Ex: ident.BitwiseAnd(bitwiseVals), Expected: exp.NewBitwiseExpression(exp.BitwiseAndOp, ident, bitwiseVals)}, + {Ex: ident.BitwiseXor(bitwiseVals), Expected: exp.NewBitwiseExpression(exp.BitwiseXorOp, ident, bitwiseVals)}, + {Ex: ident.BitwiseLeftShift(bitwiseVals), Expected: exp.NewBitwiseExpression(exp.BitwiseLeftShiftOp, ident, bitwiseVals)}, + {Ex: ident.BitwiseRightShift(bitwiseVals), Expected: exp.NewBitwiseExpression(exp.BitwiseRightShiftOp, ident, bitwiseVals)}, } for _, tc := range testCases { diff --git a/exp/literal.go b/exp/literal.go index f19775d6..da087751 100644 --- a/exp/literal.go +++ b/exp/literal.go @@ -69,3 +69,12 @@ func (l literal) IsTrue() BooleanExpression { return is(l func (l literal) IsNotTrue() BooleanExpression { return isNot(l, true) } func (l literal) IsFalse() BooleanExpression { return is(l, false) } func (l literal) IsNotFalse() BooleanExpression { return isNot(l, false) } + +func (l literal) BitwiseInversion() BitwiseExpression { return bitwiseInversion(l) } +func (l literal) BitwiseOr(val interface{}) BitwiseExpression { return bitwiseOr(l, val) } +func (l literal) BitwiseAnd(val interface{}) BitwiseExpression { return bitwiseAnd(l, val) } +func (l literal) BitwiseXor(val interface{}) BitwiseExpression { return bitwiseXor(l, val) } +func (l literal) BitwiseLeftShift(val interface{}) BitwiseExpression { return bitwiseLeftShift(l, val) } +func (l literal) BitwiseRightShift(val interface{}) BitwiseExpression { + return bitwiseRightShift(l, val) +} diff --git a/exp/literal_test.go b/exp/literal_test.go index e64166b2..42f00a8c 100644 --- a/exp/literal_test.go +++ b/exp/literal_test.go @@ -39,6 +39,7 @@ func (les *literalExpressionSuite) TestAllOthers() { rv := exp.NewRangeVal(1, 2) pattern := "literal like%" inVals := []interface{}{1, 2} + bitwiseVals := 2 testCases := []struct { Ex exp.Expression Expected exp.Expression @@ -72,6 +73,12 @@ func (les *literalExpressionSuite) TestAllOthers() { {Ex: le.IsNotTrue(), Expected: exp.NewBooleanExpression(exp.IsNotOp, le, true)}, {Ex: le.IsFalse(), Expected: exp.NewBooleanExpression(exp.IsOp, le, false)}, {Ex: le.IsNotFalse(), Expected: exp.NewBooleanExpression(exp.IsNotOp, le, false)}, + {Ex: le.BitwiseInversion(), Expected: exp.NewBitwiseExpression(exp.BitwiseInversionOp, nil, le)}, + {Ex: le.BitwiseOr(bitwiseVals), Expected: exp.NewBitwiseExpression(exp.BitwiseOrOp, le, bitwiseVals)}, + {Ex: le.BitwiseAnd(bitwiseVals), Expected: exp.NewBitwiseExpression(exp.BitwiseAndOp, le, bitwiseVals)}, + {Ex: le.BitwiseXor(bitwiseVals), Expected: exp.NewBitwiseExpression(exp.BitwiseXorOp, le, bitwiseVals)}, + {Ex: le.BitwiseLeftShift(bitwiseVals), Expected: exp.NewBitwiseExpression(exp.BitwiseLeftShiftOp, le, bitwiseVals)}, + {Ex: le.BitwiseRightShift(bitwiseVals), Expected: exp.NewBitwiseExpression(exp.BitwiseRightShiftOp, le, bitwiseVals)}, } for _, tc := range testCases { diff --git a/sqlgen/expression_sql_generator.go b/sqlgen/expression_sql_generator.go index a5f10261..82ce15c5 100644 --- a/sqlgen/expression_sql_generator.go +++ b/sqlgen/expression_sql_generator.go @@ -53,6 +53,10 @@ func errUnsupportedBooleanExpressionOperator(op exp.BooleanOperation) error { return errors.New("boolean operator '%+v' not supported", op) } +func errUnsupportedBitwiseExpressionOperator(op exp.BitwiseOperation) error { + return errors.New("bitwise operator '%+v' not supported", op) +} + func errUnsupportedRangeExpressionOperator(op exp.RangeOperation) error { return errors.New("range operator %+v not supported", op) } @@ -170,6 +174,8 @@ func (esg *expressionSQLGenerator) expressionSQL(b sb.SQLBuilder, expression exp esg.aliasedExpressionSQL(b, e) case exp.BooleanExpression: esg.booleanExpressionSQL(b, e) + case exp.BitwiseExpression: + esg.bitwiseExpressionSQL(b, e) case exp.RangeExpression: esg.rangeExpressionSQL(b, e) case exp.OrderedExpression: @@ -420,6 +426,28 @@ func (esg *expressionSQLGenerator) booleanExpressionSQL(b sb.SQLBuilder, operato b.WriteRunes(esg.dialectOptions.RightParenRune) } +// Generates SQL for a BitwiseExpresion (e.g. I("a").BitwiseOr(2) - > "a" | 2) +func (esg *expressionSQLGenerator) bitwiseExpressionSQL(b sb.SQLBuilder, operator exp.BitwiseExpression) { + b.WriteRunes(esg.dialectOptions.LeftParenRune) + + if operator.LHS() != nil { + esg.Generate(b, operator.LHS()) + b.WriteRunes(esg.dialectOptions.SpaceRune) + } + + operatorOp := operator.Op() + if val, ok := esg.dialectOptions.BitwiseOperatorLookup[operatorOp]; ok { + b.Write(val) + } else { + b.SetError(errUnsupportedBitwiseExpressionOperator(operatorOp)) + return + } + + b.WriteRunes(esg.dialectOptions.SpaceRune) + esg.Generate(b, operator.RHS()) + b.WriteRunes(esg.dialectOptions.RightParenRune) +} + // Generates SQL for a RangeExpresion (e.g. I("a").Between(RangeVal{Start:2,End:5}) -> "a" BETWEEN 2 AND 5) func (esg *expressionSQLGenerator) rangeExpressionSQL(b sb.SQLBuilder, operator exp.RangeExpression) { b.WriteRunes(esg.dialectOptions.LeftParenRune) diff --git a/sqlgen/expression_sql_generator_test.go b/sqlgen/expression_sql_generator_test.go index 1b88eada..b07dc72d 100644 --- a/sqlgen/expression_sql_generator_test.go +++ b/sqlgen/expression_sql_generator_test.go @@ -616,6 +616,41 @@ func (esgs *expressionSQLGeneratorSuite) TestGenerate_BooleanExpression() { ) } +func (esgs *expressionSQLGeneratorSuite) TestGenerate_BitwiseExpression() { + ident := exp.NewIdentifierExpression("", "", "a") + esgs.assertCases( + sqlgen.NewExpressionSQLGenerator("test", sqlgen.DefaultDialectOptions()), + expressionTestCase{val: ident.BitwiseInversion(), sql: `(~ "a")`}, + expressionTestCase{val: ident.BitwiseInversion(), sql: `(~ "a")`, isPrepared: true}, + + expressionTestCase{val: ident.BitwiseAnd(1), sql: `("a" & 1)`}, + expressionTestCase{val: ident.BitwiseAnd(1), sql: `("a" & ?)`, isPrepared: true, args: []interface{}{int64(1)}}, + + expressionTestCase{val: ident.BitwiseOr(1), sql: `("a" | 1)`}, + expressionTestCase{val: ident.BitwiseOr(1), sql: `("a" | ?)`, isPrepared: true, args: []interface{}{int64(1)}}, + + expressionTestCase{val: ident.BitwiseXor(1), sql: `("a" # 1)`}, + expressionTestCase{val: ident.BitwiseXor(1), sql: `("a" # ?)`, isPrepared: true, args: []interface{}{int64(1)}}, + + expressionTestCase{val: ident.BitwiseLeftShift(1), sql: `("a" << 1)`}, + expressionTestCase{val: ident.BitwiseLeftShift(1), sql: `("a" << ?)`, isPrepared: true, args: []interface{}{int64(1)}}, + + expressionTestCase{val: ident.BitwiseRightShift(1), sql: `("a" >> 1)`}, + expressionTestCase{val: ident.BitwiseRightShift(1), sql: `("a" >> ?)`, isPrepared: true, args: []interface{}{int64(1)}}, + ) + + opts := sqlgen.DefaultDialectOptions() + opts.BitwiseOperatorLookup = map[exp.BitwiseOperation][]byte{} + esgs.assertCases( + sqlgen.NewExpressionSQLGenerator("test", opts), + expressionTestCase{val: ident.BitwiseInversion(), err: "goqu: bitwise operator 'Inversion' not supported"}, + expressionTestCase{val: ident.BitwiseAnd(1), err: "goqu: bitwise operator 'AND' not supported"}, + expressionTestCase{val: ident.BitwiseOr(1), err: "goqu: bitwise operator 'OR' not supported"}, + expressionTestCase{val: ident.BitwiseXor(1), err: "goqu: bitwise operator 'XOR' not supported"}, + expressionTestCase{val: ident.BitwiseLeftShift(1), err: "goqu: bitwise operator 'Left Shift' not supported"}, + expressionTestCase{val: ident.BitwiseRightShift(1), err: "goqu: bitwise operator 'Right Shift' not supported"}, + ) +} func (esgs *expressionSQLGeneratorSuite) TestGenerate_RangeExpression() { betweenNum := exp.NewIdentifierExpression("", "", "a"). Between(exp.NewRangeVal(1, 2)) diff --git a/sqlgen/sql_dialect_options.go b/sqlgen/sql_dialect_options.go index b8292e5e..3e6947d1 100644 --- a/sqlgen/sql_dialect_options.go +++ b/sqlgen/sql_dialect_options.go @@ -215,6 +215,16 @@ type ( // exp.RegexpNotILikeOp: []byte("!~*"), // }) BooleanOperatorLookup map[exp.BooleanOperation][]byte + // A map used to look up BitwiseOperations and their SQL equivalents + // (Default=map[exp.BitwiseOperation][]byte{ + // exp.BitwiseInversionOp: []byte("~"), + // exp.BitwiseOrOp: []byte("|"), + // exp.BitwiseAndOp: []byte("&"), + // exp.BitwiseXorOp: []byte("#"), + // exp.BitwiseLeftShiftOp: []byte("<<"), + // exp.BitwiseRightShiftOp: []byte(">>"), + // }), + BitwiseOperatorLookup map[exp.BitwiseOperation][]byte // A map used to look up RangeOperations and their SQL equivalents // (Default=map[exp.RangeOperation][]byte{ // exp.BetweenOp: []byte("BETWEEN"), @@ -509,6 +519,14 @@ func DefaultDialectOptions() *SQLDialectOptions { exp.RegexpILikeOp: []byte("~*"), exp.RegexpNotILikeOp: []byte("!~*"), }, + BitwiseOperatorLookup: map[exp.BitwiseOperation][]byte{ + exp.BitwiseInversionOp: []byte("~"), + exp.BitwiseOrOp: []byte("|"), + exp.BitwiseAndOp: []byte("&"), + exp.BitwiseXorOp: []byte("#"), + exp.BitwiseLeftShiftOp: []byte("<<"), + exp.BitwiseRightShiftOp: []byte(">>"), + }, RangeOperatorLookup: map[exp.RangeOperation][]byte{ exp.BetweenOp: []byte("BETWEEN"), exp.NotBetweenOp: []byte("NOT BETWEEN"),