diff --git a/dialect/mysql/mysql.go b/dialect/mysql/mysql.go index d5566e49..ada571e7 100644 --- a/dialect/mysql/mysql.go +++ b/dialect/mysql/mysql.go @@ -77,6 +77,8 @@ func DialectOptions() *goqu.SQLDialectOptions { func DialectOptionsV8() *goqu.SQLDialectOptions { opts := DialectOptions() + opts.SupportsWithCTE = true + opts.SupportsWithCTERecursive = true opts.SupportsWindowFunction = true return opts } diff --git a/exp/exp.go b/exp/exp.go index 240a96a6..60834d3d 100644 --- a/exp/exp.go +++ b/exp/exp.go @@ -275,6 +275,9 @@ type ( Expression Action() ConflictAction } + ConflictNoIgnore interface { + NoIgnore() + } ConflictUpdateExpression interface { ConflictExpression TargetColumn() string diff --git a/go.mod b/go.mod index 95494c18..bb7bb19e 100644 --- a/go.mod +++ b/go.mod @@ -4,11 +4,9 @@ go 1.12 require ( github.com/DATA-DOG/go-sqlmock v1.5.0 - github.com/davecgh/go-spew v1.1.1 // indirect github.com/denisenkom/go-mssqldb v0.10.0 github.com/go-sql-driver/mysql v1.6.0 github.com/lib/pq v1.10.1 github.com/mattn/go-sqlite3 v1.14.7 github.com/stretchr/testify v1.7.0 - golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5 // indirect -) +) \ No newline at end of file diff --git a/sqlgen/expression_sql_generator.go b/sqlgen/expression_sql_generator.go index 82ce15c5..0ac89ceb 100644 --- a/sqlgen/expression_sql_generator.go +++ b/sqlgen/expression_sql_generator.go @@ -2,6 +2,7 @@ package sqlgen import ( "database/sql/driver" + "encoding/hex" "reflect" "strconv" "time" @@ -41,6 +42,10 @@ var ( ErrEmptyCaseWhens = errors.New(`when conditions not found for case statement`) ) +type HexBytes struct { + Buf []byte +} + func errUnsupportedExpressionType(e exp.Expression) error { return errors.New("unsupported expression type %T", e) } @@ -153,7 +158,12 @@ func (esg *expressionSQLGenerator) reflectSQL(b sb.SQLBuilder, val interface{}) case util.IsBool(valKind): esg.Generate(b, v.Bool()) default: - b.SetError(errors.NewEncodeError(val)) + switch t := val.(type) { + case HexBytes: + esg.literalHexBytes(b, t) + default: + b.SetError(errors.NewEncodeError(val)) + } } } @@ -344,6 +354,22 @@ func (esg *expressionSQLGenerator) literalString(b sb.SQLBuilder, s string) { b.WriteRunes(esg.dialectOptions.StringQuote) } +func (esg *expressionSQLGenerator) literalHexBytes(b sb.SQLBuilder, hbs HexBytes) { + if b.IsPrepared() { + esg.placeHolderSQL(b, hbs) + return + } + if hbs.Buf == nil { + b.WriteStrings("NULL") + return + } + + b.WriteStrings("0x") + bs := hbs.Buf + b.WriteStrings(hex.EncodeToString(bs)) + bs = bs[len(bs):] +} + // Generates SQL for a slice of bytes func (esg *expressionSQLGenerator) literalBytes(b sb.SQLBuilder, bs []byte) { if b.IsPrepared() { @@ -534,8 +560,9 @@ func (esg *expressionSQLGenerator) updateExpressionSQL(b sb.SQLBuilder, update e } // Generates SQL for a LiteralExpression -// L("a + b") -> a + b -// L("a = ?", 1) -> a = 1 +// +// L("a + b") -> a + b +// L("a = ?", 1) -> a = 1 func (esg *expressionSQLGenerator) literalExpressionSQL(b sb.SQLBuilder, literal exp.LiteralExpression) { l := literal.Literal() args := literal.Args() @@ -555,7 +582,8 @@ func (esg *expressionSQLGenerator) literalExpressionSQL(b sb.SQLBuilder, literal } // Generates SQL for a SQLFunctionExpression -// COUNT(I("a")) -> COUNT("a") +// +// COUNT(I("a")) -> COUNT("a") func (esg *expressionSQLGenerator) sqlFunctionExpressionSQL(b sb.SQLBuilder, sqlFunc exp.SQLFunctionExpression) { b.WriteStrings(sqlFunc.Name()) esg.Generate(b, sqlFunc.Args()) @@ -619,7 +647,8 @@ func (esg *expressionSQLGenerator) windowExpressionSQL(b sb.SQLBuilder, we exp.W } // Generates SQL for a CastExpression -// I("a").Cast("NUMERIC") -> CAST("a" AS NUMERIC) +// +// I("a").Cast("NUMERIC") -> CAST("a" AS NUMERIC) func (esg *expressionSQLGenerator) castExpressionSQL(b sb.SQLBuilder, cast exp.CastExpression) { b.Write(esg.dialectOptions.CastFragment).WriteRunes(esg.dialectOptions.LeftParenRune) esg.Generate(b, cast.Casted()) diff --git a/sqlgen/insert_sql_generator.go b/sqlgen/insert_sql_generator.go index 1c6105b9..467e0514 100644 --- a/sqlgen/insert_sql_generator.go +++ b/sqlgen/insert_sql_generator.go @@ -72,7 +72,14 @@ func (isg *insertSQLGenerator) Generate( // Adds the correct fragment to being an INSERT statement func (isg *insertSQLGenerator) InsertBeginSQL(b sb.SQLBuilder, o exp.ConflictExpression) { - if isg.DialectOptions().SupportsInsertIgnoreSyntax && o != nil { + isUpdate := false + forceNoIgnore := false + + if o != nil { + _, isUpdate = o.(exp.ConflictUpdateExpression) + _, forceNoIgnore = o.(exp.ConflictNoIgnore) + } + if isg.DialectOptions().SupportsInsertIgnoreSyntax && !isUpdate && !forceNoIgnore { b.Write(isg.DialectOptions().InsertIgnoreClause) } else { b.Write(isg.DialectOptions().InsertClause) diff --git a/sqlgen/insert_sql_generator_test.go b/sqlgen/insert_sql_generator_test.go index 95479c56..777d7bae 100644 --- a/sqlgen/insert_sql_generator_test.go +++ b/sqlgen/insert_sql_generator_test.go @@ -333,33 +333,33 @@ func (igs *insertSQLGeneratorSuite) TestGenerate_onConflict() { insertTestCase{ clause: icDu, - sql: `insert ignore into "test" ("a") VALUES ('a1') on conflict (test) do update set "a"='b'`, + sql: `INSERT INTO "test" ("a") VALUES ('a1') on conflict (test) do update set "a"='b'`, }, insertTestCase{ clause: icDu, - sql: `insert ignore into "test" ("a") VALUES (?) on conflict (test) do update set "a"=?`, + sql: `INSERT INTO "test" ("a") VALUES (?) on conflict (test) do update set "a"=?`, isPrepared: true, args: []interface{}{"a1", "b"}, }, insertTestCase{ clause: icDoc, - sql: `insert ignore into "test" ("a") VALUES ('a1') on conflict on constraint test do update set "a"='b'`, + sql: `INSERT INTO "test" ("a") VALUES ('a1') on conflict on constraint test do update set "a"='b'`, }, insertTestCase{ clause: icDoc, - sql: `insert ignore into "test" ("a") VALUES (?) on conflict on constraint test do update set "a"=?`, + sql: `INSERT INTO "test" ("a") VALUES (?) on conflict on constraint test do update set "a"=?`, isPrepared: true, args: []interface{}{"a1", "b"}, }, insertTestCase{ clause: icDuw, - sql: `insert ignore into "test" ("a") VALUES ('a1') on conflict (test) do update set "a"='b' WHERE ("foo" IS TRUE)`, + sql: `INSERT INTO "test" ("a") VALUES ('a1') on conflict (test) do update set "a"='b' WHERE ("foo" IS TRUE)`, }, insertTestCase{ clause: icDuw, - sql: `insert ignore into "test" ("a") VALUES (?) on conflict (test) do update set "a"=? WHERE ("foo" IS TRUE)`, + sql: `INSERT INTO "test" ("a") VALUES (?) on conflict (test) do update set "a"=? WHERE ("foo" IS TRUE)`, isPrepared: true, args: []interface{}{"a1", "b"}, },