diff --git a/docs/howto/insert.md b/docs/howto/insert.md index 7bb02d6745..0a2945b509 100644 --- a/docs/howto/insert.md +++ b/docs/howto/insert.md @@ -208,6 +208,10 @@ and use SHOW WARNINGS to check for any problems and roll back if necessary. Check the [error handling](https://dev.mysql.com/doc/refman/8.0/en/load-data.html#load-data-error-handling) documentation for more information. +Writing the query as `REPLACE INTO` generates `LOAD DATA ... REPLACE INTO TABLE`, +which overwrites rows that collide on a primary or unique key instead of skipping +them. + ```sql CREATE TABLE foo (a text, b integer, c DATETIME, d DATE); diff --git a/internal/cmd/shim.go b/internal/cmd/shim.go index 654500429a..7ca3d2f14b 100644 --- a/internal/cmd/shim.go +++ b/internal/cmd/shim.go @@ -161,6 +161,7 @@ func pluginQueries(r *compiler.Result) []*plugin.Query { Params: params, Filename: q.Metadata.Filename, InsertIntoTable: iit, + InsertIsReplace: q.InsertIsReplace(), }) } return out diff --git a/internal/codegen/golang/query.go b/internal/codegen/golang/query.go index 27c596c24e..8b6b29908d 100644 --- a/internal/codegen/golang/query.go +++ b/internal/codegen/golang/query.go @@ -279,6 +279,8 @@ type Query struct { Arg QueryValue // Used for :copyfrom Table *plugin.Identifier + // Used for :copyfrom, true when the query was written as REPLACE INTO + InsertIsReplace bool } func (q Query) hasRetType() bool { diff --git a/internal/codegen/golang/result.go b/internal/codegen/golang/result.go index c5126602da..31c1a5599e 100644 --- a/internal/codegen/golang/result.go +++ b/internal/codegen/golang/result.go @@ -218,14 +218,15 @@ func buildQueries(req *plugin.GenerateRequest, options *opts.Options, enums []En } gq := Query{ - Cmd: query.Cmd, - ConstantName: constantName, - FieldName: sdk.LowerTitle(query.Name) + "Stmt", - MethodName: query.Name, - SourceName: query.Filename, - SQL: query.Text, - Comments: comments, - Table: query.InsertIntoTable, + Cmd: query.Cmd, + ConstantName: constantName, + FieldName: sdk.LowerTitle(query.Name) + "Stmt", + MethodName: query.Name, + SourceName: query.Filename, + SQL: query.Text, + Comments: comments, + Table: query.InsertIntoTable, + InsertIsReplace: query.InsertIsReplace, } sqlpkg := parseDriver(options.SqlPackage) diff --git a/internal/codegen/golang/templates/go-sql-driver-mysql/copyfromCopy.tmpl b/internal/codegen/golang/templates/go-sql-driver-mysql/copyfromCopy.tmpl index e21475b148..0aa89f3805 100644 --- a/internal/codegen/golang/templates/go-sql-driver-mysql/copyfromCopy.tmpl +++ b/internal/codegen/golang/templates/go-sql-driver-mysql/copyfromCopy.tmpl @@ -25,9 +25,16 @@ func convertRowsFor{{.MethodName}}(w *io.PipeWriter, {{.Arg.SlicePair}}) { {{end -}} // {{.MethodName}} uses MySQL's LOAD DATA LOCAL INFILE and is not atomic. // +{{if .InsertIsReplace -}} +// Errors are treated as warnings and insertion will continue, even without an +// error for some cases. Rows that collide on a primary or unique key replace +// the existing row. Use this in a transaction and use SHOW WARNINGS to check +// for any problems and roll back if you want to. +{{else -}} // Errors and duplicate keys are treated as warnings and insertion will // continue, even without an error for some cases. Use this in a transaction // and use SHOW WARNINGS to check for any problems and roll back if you want to. +{{end -}} // // Check the documentation for more information: // https://dev.mysql.com/doc/refman/8.0/en/load-data.html#load-data-error-handling @@ -40,7 +47,7 @@ func (q *Queries) {{.MethodName}}(ctx context.Context{{if $.EmitMethodsWithDBArg go convertRowsFor{{.MethodName}}(pw, {{.Arg.Name}}) // The string interpolation is necessary because LOAD DATA INFILE requires // the file name to be given as a literal string. - result, err := {{if (not $.EmitMethodsWithDBArgument)}}q.{{end}}db.ExecContext(ctx, fmt.Sprintf("LOAD DATA LOCAL INFILE '%s' INTO TABLE {{.TableIdentifierForMySQL}} %s ({{range $index, $name := .Arg.ColumnNames}}{{if gt $index 0}}, {{end}}{{$name}}{{end}})", "Reader::" + rh, mysqltsv.Escaping)) + result, err := {{if (not $.EmitMethodsWithDBArgument)}}q.{{end}}db.ExecContext(ctx, fmt.Sprintf("LOAD DATA LOCAL INFILE '%s' {{if .InsertIsReplace}}REPLACE {{end}}INTO TABLE {{.TableIdentifierForMySQL}} %s ({{range $index, $name := .Arg.ColumnNames}}{{if gt $index 0}}, {{end}}{{$name}}{{end}})", "Reader::" + rh, mysqltsv.Escaping)) if err != nil { return 0, err } diff --git a/internal/compiler/query.go b/internal/compiler/query.go index b3cf9d6154..08211a989b 100644 --- a/internal/compiler/query.go +++ b/internal/compiler/query.go @@ -55,6 +55,17 @@ type Query struct { RawStmt *ast.RawStmt } +// InsertIsReplace reports whether the query was written as REPLACE INTO. MySQL +// renders a :copyfrom for such a query as LOAD DATA ... REPLACE INTO TABLE, so +// that rows colliding on a key overwrite instead of being skipped. +func (q *Query) InsertIsReplace() bool { + if q.RawStmt == nil { + return false + } + ins, ok := q.RawStmt.Stmt.(*ast.InsertStmt) + return ok && ins.IsReplace +} + type Parameter struct { Number int Column *Column diff --git a/internal/endtoend/testdata/codegen_json/gen/codegen.json b/internal/endtoend/testdata/codegen_json/gen/codegen.json index 1e3a217541..558daccca4 100644 --- a/internal/endtoend/testdata/codegen_json/gen/codegen.json +++ b/internal/endtoend/testdata/codegen_json/gen/codegen.json @@ -66389,7 +66389,8 @@ ], "comments": [], "filename": "query.sql", - "insert_into_table": null + "insert_into_table": null, + "insert_is_replace": false }, { "text": "SELECT id, name, bio FROM authors\nORDER BY name", @@ -66478,7 +66479,8 @@ "params": [], "comments": [], "filename": "query.sql", - "insert_into_table": null + "insert_into_table": null, + "insert_is_replace": false }, { "text": "INSERT INTO authors (\n name, bio\n) VALUES (\n $1, $2\n)\nRETURNING id, name, bio", @@ -66630,7 +66632,8 @@ "catalog": "", "schema": "", "name": "authors" - } + }, + "insert_is_replace": false }, { "text": "DELETE FROM authors\nWHERE id = $1", @@ -66670,7 +66673,8 @@ ], "comments": [], "filename": "query.sql", - "insert_into_table": null + "insert_into_table": null, + "insert_is_replace": false } ], "sqlc_version": "v1.31.1", diff --git a/internal/endtoend/testdata/copyfrom/mysql/go/copyfrom.go b/internal/endtoend/testdata/copyfrom/mysql/go/copyfrom.go index b5ce436565..292b79fd10 100644 --- a/internal/endtoend/testdata/copyfrom/mysql/go/copyfrom.go +++ b/internal/endtoend/testdata/copyfrom/mysql/go/copyfrom.go @@ -86,3 +86,41 @@ func (q *Queries) InsertValues(ctx context.Context, arg []InsertValuesParams) (i } return result.RowsAffected() } + +var readerHandlerSequenceForReplaceValues uint32 = 1 + +func convertRowsForReplaceValues(w *io.PipeWriter, arg []ReplaceValuesParams) { + e := mysqltsv.NewEncoder(w, 4, nil) + for _, row := range arg { + e.AppendValue(row.A) + e.AppendValue(row.B) + e.AppendValue(row.C) + e.AppendValue(row.D) + } + w.CloseWithError(e.Close()) +} + +// ReplaceValues uses MySQL's LOAD DATA LOCAL INFILE and is not atomic. +// +// Errors are treated as warnings and insertion will continue, even without an +// error for some cases. Rows that collide on a primary or unique key replace +// the existing row. Use this in a transaction and use SHOW WARNINGS to check +// for any problems and roll back if you want to. +// +// Check the documentation for more information: +// https://dev.mysql.com/doc/refman/8.0/en/load-data.html#load-data-error-handling +func (q *Queries) ReplaceValues(ctx context.Context, arg []ReplaceValuesParams) (int64, error) { + pr, pw := io.Pipe() + defer pr.Close() + rh := fmt.Sprintf("ReplaceValues_%d", atomic.AddUint32(&readerHandlerSequenceForReplaceValues, 1)) + mysql.RegisterReaderHandler(rh, func() io.Reader { return pr }) + defer mysql.DeregisterReaderHandler(rh) + go convertRowsForReplaceValues(pw, arg) + // The string interpolation is necessary because LOAD DATA INFILE requires + // the file name to be given as a literal string. + result, err := q.db.ExecContext(ctx, fmt.Sprintf("LOAD DATA LOCAL INFILE '%s' REPLACE INTO TABLE `foo` %s (a, b, c, d)", "Reader::"+rh, mysqltsv.Escaping)) + if err != nil { + return 0, err + } + return result.RowsAffected() +} diff --git a/internal/endtoend/testdata/copyfrom/mysql/go/query.sql.go b/internal/endtoend/testdata/copyfrom/mysql/go/query.sql.go index 91bd6628f8..624274fd3c 100644 --- a/internal/endtoend/testdata/copyfrom/mysql/go/query.sql.go +++ b/internal/endtoend/testdata/copyfrom/mysql/go/query.sql.go @@ -23,3 +23,14 @@ type InsertValuesParams struct { C sql.NullTime D sql.NullTime } + +const replaceValues = `-- name: ReplaceValues :copyfrom +REPLACE INTO foo (a, b, c, d) VALUES (?, ?, ?, ?) +` + +type ReplaceValuesParams struct { + A sql.NullString + B sql.NullInt32 + C sql.NullTime + D sql.NullTime +} diff --git a/internal/endtoend/testdata/copyfrom/mysql/query.sql b/internal/endtoend/testdata/copyfrom/mysql/query.sql index 4c5fd5f54c..2a83d11b10 100644 --- a/internal/endtoend/testdata/copyfrom/mysql/query.sql +++ b/internal/endtoend/testdata/copyfrom/mysql/query.sql @@ -3,3 +3,6 @@ INSERT INTO foo (a, b, c, d) VALUES (?, ?, ?, ?); -- name: InsertSingleValue :copyfrom INSERT INTO foo (a) VALUES (?); + +-- name: ReplaceValues :copyfrom +REPLACE INTO foo (a, b, c, d) VALUES (?, ?, ?, ?); diff --git a/internal/endtoend/testdata/process_plugin_sqlc_gen_json/gen/codegen.json b/internal/endtoend/testdata/process_plugin_sqlc_gen_json/gen/codegen.json index c4556eebee..db3917b62d 100644 --- a/internal/endtoend/testdata/process_plugin_sqlc_gen_json/gen/codegen.json +++ b/internal/endtoend/testdata/process_plugin_sqlc_gen_json/gen/codegen.json @@ -66391,7 +66391,8 @@ ], "comments": [], "filename": "query.sql", - "insert_into_table": null + "insert_into_table": null, + "insert_is_replace": false }, { "text": "SELECT id, name, bio FROM authors\nORDER BY name", @@ -66480,7 +66481,8 @@ "params": [], "comments": [], "filename": "query.sql", - "insert_into_table": null + "insert_into_table": null, + "insert_is_replace": false }, { "text": "INSERT INTO authors (\n name, bio\n) VALUES (\n $1, $2\n)\nRETURNING id, name, bio", @@ -66632,7 +66634,8 @@ "catalog": "", "schema": "", "name": "authors" - } + }, + "insert_is_replace": false }, { "text": "DELETE FROM authors\nWHERE id = $1", @@ -66672,7 +66675,8 @@ ], "comments": [], "filename": "query.sql", - "insert_into_table": null + "insert_into_table": null, + "insert_is_replace": false } ], "sqlc_version": "v1.31.1", diff --git a/internal/engine/dolphin/convert.go b/internal/engine/dolphin/convert.go index cfd83b5c4d..7efee84793 100644 --- a/internal/engine/dolphin/convert.go +++ b/internal/engine/dolphin/convert.go @@ -492,6 +492,7 @@ func (c *cc) convertInsertStmt(n *pcast.InsertStmt) *ast.InsertStmt { Relation: rangeVar, Cols: c.convertColumnNames(n.Columns), ReturningList: &ast.List{}, + IsReplace: n.IsReplace, } if ss, ok := c.convert(n.Select).(*ast.SelectStmt); ok { ss.ValuesLists = c.convertLists(n.Lists) diff --git a/internal/plugin/codegen.pb.go b/internal/plugin/codegen.pb.go index 525ffc72ef..87d2f8feeb 100644 --- a/internal/plugin/codegen.pb.go +++ b/internal/plugin/codegen.pb.go @@ -816,6 +816,7 @@ type Query struct { Comments []string `protobuf:"bytes,6,rep,name=comments,proto3" json:"comments,omitempty"` Filename string `protobuf:"bytes,7,opt,name=filename,proto3" json:"filename,omitempty"` InsertIntoTable *Identifier `protobuf:"bytes,8,opt,name=insert_into_table,proto3" json:"insert_into_table,omitempty"` + InsertIsReplace bool `protobuf:"varint,9,opt,name=insert_is_replace,proto3" json:"insert_is_replace,omitempty"` } func (x *Query) Reset() { @@ -906,6 +907,13 @@ func (x *Query) GetInsertIntoTable() *Identifier { return nil } +func (x *Query) GetInsertIsReplace() bool { + if x != nil { + return x.InsertIsReplace + } + return false +} + type Parameter struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -1308,7 +1316,7 @@ var file_plugin_codegen_proto_rawDesc = []byte{ 0x61, 0x6d, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x75, 0x6e, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x18, 0x10, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x75, 0x6e, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x61, 0x72, 0x72, 0x61, 0x79, 0x5f, 0x64, 0x69, 0x6d, 0x73, 0x18, 0x11, 0x20, - 0x01, 0x28, 0x05, 0x52, 0x09, 0x61, 0x72, 0x72, 0x61, 0x79, 0x44, 0x69, 0x6d, 0x73, 0x22, 0x94, + 0x01, 0x28, 0x05, 0x52, 0x09, 0x61, 0x72, 0x72, 0x61, 0x79, 0x44, 0x69, 0x6d, 0x73, 0x22, 0xc2, 0x02, 0x0a, 0x05, 0x51, 0x75, 0x65, 0x72, 0x79, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x65, 0x78, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x65, 0x78, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, @@ -1326,45 +1334,48 @@ var file_plugin_codegen_proto_rawDesc = []byte{ 0x74, 0x6f, 0x5f, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x2e, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x52, 0x11, 0x69, 0x6e, 0x73, 0x65, 0x72, 0x74, 0x5f, 0x69, 0x6e, 0x74, 0x6f, 0x5f, - 0x74, 0x61, 0x62, 0x6c, 0x65, 0x22, 0x4b, 0x0a, 0x09, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, - 0x65, 0x72, 0x12, 0x16, 0x0a, 0x06, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x05, 0x52, 0x06, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x12, 0x26, 0x0a, 0x06, 0x63, 0x6f, - 0x6c, 0x75, 0x6d, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x70, 0x6c, 0x75, - 0x67, 0x69, 0x6e, 0x2e, 0x43, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x52, 0x06, 0x63, 0x6f, 0x6c, 0x75, - 0x6d, 0x6e, 0x22, 0x87, 0x02, 0x0a, 0x0f, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2c, 0x0a, 0x08, 0x73, 0x65, 0x74, 0x74, 0x69, 0x6e, - 0x67, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x70, 0x6c, 0x75, 0x67, 0x69, - 0x6e, 0x2e, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x52, 0x08, 0x73, 0x65, 0x74, 0x74, - 0x69, 0x6e, 0x67, 0x73, 0x12, 0x29, 0x0a, 0x07, 0x63, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x2e, 0x43, - 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x52, 0x07, 0x63, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x12, - 0x27, 0x0a, 0x07, 0x71, 0x75, 0x65, 0x72, 0x69, 0x65, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, - 0x32, 0x0d, 0x2e, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x52, - 0x07, 0x71, 0x75, 0x65, 0x72, 0x69, 0x65, 0x73, 0x12, 0x22, 0x0a, 0x0c, 0x73, 0x71, 0x6c, 0x63, - 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, - 0x73, 0x71, 0x6c, 0x63, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x26, 0x0a, 0x0e, - 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x5f, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x05, - 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0e, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x5f, 0x6f, 0x70, 0x74, - 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x26, 0x0a, 0x0e, 0x67, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x5f, 0x6f, - 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0e, 0x67, 0x6c, - 0x6f, 0x62, 0x61, 0x6c, 0x5f, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0x36, 0x0a, 0x10, - 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x12, 0x22, 0x0a, 0x05, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, - 0x0c, 0x2e, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x2e, 0x46, 0x69, 0x6c, 0x65, 0x52, 0x05, 0x66, - 0x69, 0x6c, 0x65, 0x73, 0x32, 0x4f, 0x0a, 0x0e, 0x43, 0x6f, 0x64, 0x65, 0x67, 0x65, 0x6e, 0x53, - 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x3d, 0x0a, 0x08, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, - 0x74, 0x65, 0x12, 0x17, 0x2e, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x2e, 0x47, 0x65, 0x6e, 0x65, - 0x72, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x18, 0x2e, 0x70, 0x6c, - 0x75, 0x67, 0x69, 0x6e, 0x2e, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x7c, 0x0a, 0x0a, 0x63, 0x6f, 0x6d, 0x2e, 0x70, 0x6c, 0x75, - 0x67, 0x69, 0x6e, 0x42, 0x0c, 0x43, 0x6f, 0x64, 0x65, 0x67, 0x65, 0x6e, 0x50, 0x72, 0x6f, 0x74, - 0x6f, 0x50, 0x01, 0x5a, 0x28, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, - 0x73, 0x71, 0x6c, 0x63, 0x2d, 0x64, 0x65, 0x76, 0x2f, 0x73, 0x71, 0x6c, 0x63, 0x2f, 0x69, 0x6e, - 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0xa2, 0x02, 0x03, - 0x50, 0x58, 0x58, 0xaa, 0x02, 0x06, 0x50, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0xca, 0x02, 0x06, 0x50, - 0x6c, 0x75, 0x67, 0x69, 0x6e, 0xe2, 0x02, 0x12, 0x50, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x5c, 0x47, - 0x50, 0x42, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0xea, 0x02, 0x06, 0x50, 0x6c, 0x75, - 0x67, 0x69, 0x6e, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x74, 0x61, 0x62, 0x6c, 0x65, 0x12, 0x2c, 0x0a, 0x11, 0x69, 0x6e, 0x73, 0x65, 0x72, 0x74, 0x5f, + 0x69, 0x73, 0x5f, 0x72, 0x65, 0x70, 0x6c, 0x61, 0x63, 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, 0x08, + 0x52, 0x11, 0x69, 0x6e, 0x73, 0x65, 0x72, 0x74, 0x5f, 0x69, 0x73, 0x5f, 0x72, 0x65, 0x70, 0x6c, + 0x61, 0x63, 0x65, 0x22, 0x4b, 0x0a, 0x09, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, + 0x12, 0x16, 0x0a, 0x06, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, + 0x52, 0x06, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x12, 0x26, 0x0a, 0x06, 0x63, 0x6f, 0x6c, 0x75, + 0x6d, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x70, 0x6c, 0x75, 0x67, 0x69, + 0x6e, 0x2e, 0x43, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x52, 0x06, 0x63, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, + 0x22, 0x87, 0x02, 0x0a, 0x0f, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x12, 0x2c, 0x0a, 0x08, 0x73, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x2e, + 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x52, 0x08, 0x73, 0x65, 0x74, 0x74, 0x69, 0x6e, + 0x67, 0x73, 0x12, 0x29, 0x0a, 0x07, 0x63, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x2e, 0x43, 0x61, 0x74, + 0x61, 0x6c, 0x6f, 0x67, 0x52, 0x07, 0x63, 0x61, 0x74, 0x61, 0x6c, 0x6f, 0x67, 0x12, 0x27, 0x0a, + 0x07, 0x71, 0x75, 0x65, 0x72, 0x69, 0x65, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0d, + 0x2e, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x52, 0x07, 0x71, + 0x75, 0x65, 0x72, 0x69, 0x65, 0x73, 0x12, 0x22, 0x0a, 0x0c, 0x73, 0x71, 0x6c, 0x63, 0x5f, 0x76, + 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x73, 0x71, + 0x6c, 0x63, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x26, 0x0a, 0x0e, 0x70, 0x6c, + 0x75, 0x67, 0x69, 0x6e, 0x5f, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x05, 0x20, 0x01, + 0x28, 0x0c, 0x52, 0x0e, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x5f, 0x6f, 0x70, 0x74, 0x69, 0x6f, + 0x6e, 0x73, 0x12, 0x26, 0x0a, 0x0e, 0x67, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x5f, 0x6f, 0x70, 0x74, + 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0e, 0x67, 0x6c, 0x6f, 0x62, + 0x61, 0x6c, 0x5f, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0x36, 0x0a, 0x10, 0x47, 0x65, + 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x22, + 0x0a, 0x05, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0c, 0x2e, + 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x2e, 0x46, 0x69, 0x6c, 0x65, 0x52, 0x05, 0x66, 0x69, 0x6c, + 0x65, 0x73, 0x32, 0x4f, 0x0a, 0x0e, 0x43, 0x6f, 0x64, 0x65, 0x67, 0x65, 0x6e, 0x53, 0x65, 0x72, + 0x76, 0x69, 0x63, 0x65, 0x12, 0x3d, 0x0a, 0x08, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, + 0x12, 0x17, 0x2e, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x2e, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, + 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x18, 0x2e, 0x70, 0x6c, 0x75, 0x67, + 0x69, 0x6e, 0x2e, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x42, 0x7c, 0x0a, 0x0a, 0x63, 0x6f, 0x6d, 0x2e, 0x70, 0x6c, 0x75, 0x67, 0x69, + 0x6e, 0x42, 0x0c, 0x43, 0x6f, 0x64, 0x65, 0x67, 0x65, 0x6e, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, + 0x01, 0x5a, 0x28, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x73, 0x71, + 0x6c, 0x63, 0x2d, 0x64, 0x65, 0x76, 0x2f, 0x73, 0x71, 0x6c, 0x63, 0x2f, 0x69, 0x6e, 0x74, 0x65, + 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0xa2, 0x02, 0x03, 0x50, 0x58, + 0x58, 0xaa, 0x02, 0x06, 0x50, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0xca, 0x02, 0x06, 0x50, 0x6c, 0x75, + 0x67, 0x69, 0x6e, 0xe2, 0x02, 0x12, 0x50, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x5c, 0x47, 0x50, 0x42, + 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0xea, 0x02, 0x06, 0x50, 0x6c, 0x75, 0x67, 0x69, + 0x6e, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( diff --git a/internal/sql/ast/insert_stmt.go b/internal/sql/ast/insert_stmt.go index 4d5c8d1df2..28313fc3bf 100644 --- a/internal/sql/ast/insert_stmt.go +++ b/internal/sql/ast/insert_stmt.go @@ -12,6 +12,7 @@ type InsertStmt struct { WithClause *WithClause Override OverridingKind DefaultValues bool // SQLite-specific: INSERT INTO ... DEFAULT VALUES + IsReplace bool // MySQL-specific: REPLACE INTO ... instead of INSERT INTO ... } func (n *InsertStmt) Pos() int { @@ -28,7 +29,11 @@ func (n *InsertStmt) Format(buf *TrackedBuffer, d format.Dialect) { buf.WriteString(" ") } - buf.WriteString("INSERT INTO ") + if n.IsReplace { + buf.WriteString("REPLACE INTO ") + } else { + buf.WriteString("INSERT INTO ") + } if n.Relation != nil { buf.astFormat(n.Relation, d) } diff --git a/protos/plugin/codegen.proto b/protos/plugin/codegen.proto index e6faf19bad..2707f813fe 100644 --- a/protos/plugin/codegen.proto +++ b/protos/plugin/codegen.proto @@ -111,6 +111,7 @@ message Query { repeated string comments = 6 [json_name = "comments"]; string filename = 7 [json_name = "filename"]; Identifier insert_into_table = 8 [json_name = "insert_into_table"]; + bool insert_is_replace = 9 [json_name = "insert_is_replace"]; } message Parameter {