From 4e1ff6502323776efd256581f475bb875bf35234 Mon Sep 17 00:00:00 2001 From: go-jet Date: Tue, 23 Aug 2022 12:23:46 +0200 Subject: [PATCH] [MySQL] Add NEW alias for the rows to be inserted. --- generator/template/file_templates.go | 78 +--------------------------- generator/template/process.go | 34 ++++++------ internal/jet/clause.go | 7 +++ mysql/insert_statement.go | 8 ++- tests/mysql/generator_test.go | 54 ++++++++++++++----- tests/mysql/insert_test.go | 61 +++++++++++++++++++++- 6 files changed, 135 insertions(+), 107 deletions(-) diff --git a/generator/template/file_templates.go b/generator/template/file_templates.go index d1f12603..35b4dcd4 100644 --- a/generator/template/file_templates.go +++ b/generator/template/file_templates.go @@ -26,80 +26,6 @@ import ( var {{tableTemplate.InstanceName}} = new{{tableTemplate.TypeName}}("{{schemaName}}", "{{.Name}}", "") -type {{tableTemplate.TypeName}} struct { - {{dialect.PackageName}}.Table - - //Columns -{{- range $i, $c := .Columns}} -{{- $field := columnField $c}} - {{$field.Name}} {{dialect.PackageName}}.Column{{$field.Type}} -{{- end}} - - AllColumns {{dialect.PackageName}}.ColumnList - MutableColumns {{dialect.PackageName}}.ColumnList -} - -// AS creates new {{tableTemplate.TypeName}} with assigned alias -func (a {{tableTemplate.TypeName}}) AS(alias string) {{tableTemplate.TypeName}} { - return new{{tableTemplate.TypeName}}(a.SchemaName(), a.TableName(), alias) -} - -// Schema creates new {{tableTemplate.TypeName}} with assigned schema name -func (a {{tableTemplate.TypeName}}) FromSchema(schemaName string) {{tableTemplate.TypeName}} { - return new{{tableTemplate.TypeName}}(schemaName, a.TableName(), a.Alias()) -} - -// WithPrefix creates new {{tableTemplate.TypeName}} with assigned table prefix -func (a {{tableTemplate.TypeName}}) WithPrefix(prefix string) {{tableTemplate.TypeName}} { - return new{{tableTemplate.TypeName}}(a.SchemaName(), prefix+a.TableName(), a.TableName()) -} - -// WithSuffix creates new {{tableTemplate.TypeName}} with assigned table suffix -func (a {{tableTemplate.TypeName}}) WithSuffix(suffix string) {{tableTemplate.TypeName}} { - return new{{tableTemplate.TypeName}}(a.SchemaName(), a.TableName()+suffix, a.TableName()) -} - -func new{{tableTemplate.TypeName}}(schemaName, tableName, alias string) {{tableTemplate.TypeName}} { - var ( -{{- range $i, $c := .Columns}} -{{- $field := columnField $c}} - {{$field.Name}}Column = {{dialect.PackageName}}.{{$field.Type}}Column("{{$c.Name}}") -{{- end}} - allColumns = {{dialect.PackageName}}.ColumnList{ {{template "column-list" .Columns}} } - mutableColumns = {{dialect.PackageName}}.ColumnList{ {{template "column-list" .MutableColumns}} } - ) - - return {{tableTemplate.TypeName}}{ - Table: {{dialect.PackageName}}.NewTable(schemaName, tableName, alias, allColumns...), - - //Columns -{{- range $i, $c := .Columns}} -{{- $field := columnField $c}} - {{$field.Name}}: {{$field.Name}}Column, -{{- end}} - - AllColumns: allColumns, - MutableColumns: mutableColumns, - } -} -` - -var tableSQLBuilderTemplateWithEXCLUDED = ` -{{define "column-list" -}} - {{- range $i, $c := . }} -{{- $field := columnField $c}} - {{- if gt $i 0 }}, {{end}}{{$field.Name}}Column - {{- end}} -{{- end}} - -package {{package}} - -import ( - "github.com/go-jet/jet/v2/{{dialect.PackageName}}" -) - -var {{tableTemplate.InstanceName}} = new{{tableTemplate.TypeName}}("{{schemaName}}", "{{.Name}}", "") - type {{structImplName}} struct { {{dialect.PackageName}}.Table @@ -116,7 +42,7 @@ type {{structImplName}} struct { type {{tableTemplate.TypeName}} struct { {{structImplName}} - EXCLUDED {{structImplName}} + {{toUpper insertedRowAlias}} {{structImplName}} } // AS creates new {{tableTemplate.TypeName}} with assigned alias @@ -142,7 +68,7 @@ func (a {{tableTemplate.TypeName}}) WithSuffix(suffix string) *{{tableTemplate.T func new{{tableTemplate.TypeName}}(schemaName, tableName, alias string) *{{tableTemplate.TypeName}} { return &{{tableTemplate.TypeName}}{ {{structImplName}}: new{{tableTemplate.TypeName}}Impl(schemaName, tableName, alias), - EXCLUDED: new{{tableTemplate.TypeName}}Impl("", "excluded", ""), + {{toUpper insertedRowAlias}}: new{{tableTemplate.TypeName}}Impl("", "{{insertedRowAlias}}", ""), } } diff --git a/generator/template/process.go b/generator/template/process.go index 46a598de..09636dc4 100644 --- a/generator/template/process.go +++ b/generator/template/process.go @@ -120,29 +120,29 @@ func processTableSQLBuilder(fileTypes, dirPath string, for _, tableMetaData := range tablesMetaData { - var tableSQLBuilderTemplate TableSQLBuilder + var tableSQLBuilder TableSQLBuilder if fileTypes == "view" { - tableSQLBuilderTemplate = sqlBuilderTemplate.View(tableMetaData) + tableSQLBuilder = sqlBuilderTemplate.View(tableMetaData) } else { - tableSQLBuilderTemplate = sqlBuilderTemplate.Table(tableMetaData) + tableSQLBuilder = sqlBuilderTemplate.Table(tableMetaData) } - if tableSQLBuilderTemplate.Skip { + if tableSQLBuilder.Skip { continue } - tableSQLBuilderPath := path.Join(dirPath, tableSQLBuilderTemplate.Path) + tableSQLBuilderPath := path.Join(dirPath, tableSQLBuilder.Path) err := utils.EnsureDirPath(tableSQLBuilderPath) throw.OnError(err) text, err := generateTemplate( - autoGenWarningTemplate+getTableSQLBuilderTemplate(dialect), + autoGenWarningTemplate+tableSQLBuilderTemplate, tableMetaData, template.FuncMap{ "package": func() string { - return tableSQLBuilderTemplate.PackageName() + return tableSQLBuilder.PackageName() }, "dialect": func() jet.Dialect { return dialect @@ -151,29 +151,33 @@ func processTableSQLBuilder(fileTypes, dirPath string, return schemaMetaData.Name }, "tableTemplate": func() TableSQLBuilder { - return tableSQLBuilderTemplate + return tableSQLBuilder }, "structImplName": func() string { // postgres only - structName := tableSQLBuilderTemplate.TypeName + structName := tableSQLBuilder.TypeName return string(strings.ToLower(structName)[0]) + structName[1:] }, "columnField": func(columnMetaData metadata.Column) TableSQLBuilderColumn { - return tableSQLBuilderTemplate.Column(columnMetaData) + return tableSQLBuilder.Column(columnMetaData) + }, + "toUpper": strings.ToUpper, + "insertedRowAlias": func() string { + return insertedRowAlias(dialect) }, }) throw.OnError(err) - err = utils.SaveGoFile(tableSQLBuilderPath, tableSQLBuilderTemplate.FileName, text) + err = utils.SaveGoFile(tableSQLBuilderPath, tableSQLBuilder.FileName, text) throw.OnError(err) } } -func getTableSQLBuilderTemplate(dialect jet.Dialect) string { - if dialect.Name() == "PostgreSQL" || dialect.Name() == "SQLite" { - return tableSQLBuilderTemplateWithEXCLUDED +func insertedRowAlias(dialect jet.Dialect) string { + if dialect.Name() == "MySQL" { + return "new" } - return tableSQLBuilderTemplate + return "excluded" } func processTableModels(fileTypes, modelDirPath string, tablesMetaData []metadata.Table, modelTemplate Model) { diff --git a/internal/jet/clause.go b/internal/jet/clause.go index aa450055..85dd5341 100644 --- a/internal/jet/clause.go +++ b/internal/jet/clause.go @@ -392,6 +392,7 @@ func (v *ClauseValuesQuery) Serialize(statementType StatementType, out *SQLBuild // ClauseValues struct type ClauseValues struct { Rows [][]Serializer + As string } // Serialize serializes clause into SQLBuilder @@ -417,6 +418,12 @@ func (v *ClauseValues) Serialize(statementType StatementType, out *SQLBuilder, o out.WriteByte(')') } + + if len(v.As) > 0 { + out.WriteString("AS") + out.WriteIdentifier(v.As) + } + out.DecreaseIdent(7) } diff --git a/mysql/insert_statement.go b/mysql/insert_statement.go index 8495e040..273374f5 100644 --- a/mysql/insert_statement.go +++ b/mysql/insert_statement.go @@ -12,6 +12,7 @@ type InsertStatement interface { // If data is not struct or there is no field for every column selected, this method will panic. MODEL(data interface{}) InsertStatement MODELS(data interface{}) InsertStatement + AS_NEW() InsertStatement ON_DUPLICATE_KEY_UPDATE(assigments ...ColumnAssigment) InsertStatement @@ -52,6 +53,11 @@ func (is *insertStatementImpl) MODELS(data interface{}) InsertStatement { return is } +func (is *insertStatementImpl) AS_NEW() InsertStatement { + is.ValuesQuery.As = "new" + return is +} + func (is *insertStatementImpl) ON_DUPLICATE_KEY_UPDATE(assigments ...ColumnAssigment) InsertStatement { is.OnDuplicateKey = assigments return is @@ -79,7 +85,7 @@ func (s onDuplicateKeyUpdateClause) Serialize(statementType jet.StatementType, o out.NewLine() } - jet.Serialize(assigment, statementType, out, jet.ShortName.WithFallTrough(options)...) + jet.Serialize(assigment, statementType, out, jet.FallTrough(options)...) } out.DecreaseIdent(24) diff --git a/tests/mysql/generator_test.go b/tests/mysql/generator_test.go index e8f8d8f7..acbe04bc 100644 --- a/tests/mysql/generator_test.go +++ b/tests/mysql/generator_test.go @@ -238,7 +238,7 @@ import ( var Actor = newActorTable("dvds", "actor", "") -type ActorTable struct { +type actorTable struct { mysql.Table //Columns @@ -251,27 +251,40 @@ type ActorTable struct { MutableColumns mysql.ColumnList } +type ActorTable struct { + actorTable + + NEW actorTable +} + // AS creates new ActorTable with assigned alias -func (a ActorTable) AS(alias string) ActorTable { +func (a ActorTable) AS(alias string) *ActorTable { return newActorTable(a.SchemaName(), a.TableName(), alias) } // Schema creates new ActorTable with assigned schema name -func (a ActorTable) FromSchema(schemaName string) ActorTable { +func (a ActorTable) FromSchema(schemaName string) *ActorTable { return newActorTable(schemaName, a.TableName(), a.Alias()) } // WithPrefix creates new ActorTable with assigned table prefix -func (a ActorTable) WithPrefix(prefix string) ActorTable { +func (a ActorTable) WithPrefix(prefix string) *ActorTable { return newActorTable(a.SchemaName(), prefix+a.TableName(), a.TableName()) } // WithSuffix creates new ActorTable with assigned table suffix -func (a ActorTable) WithSuffix(suffix string) ActorTable { +func (a ActorTable) WithSuffix(suffix string) *ActorTable { return newActorTable(a.SchemaName(), a.TableName()+suffix, a.TableName()) } -func newActorTable(schemaName, tableName, alias string) ActorTable { +func newActorTable(schemaName, tableName, alias string) *ActorTable { + return &ActorTable{ + actorTable: newActorTableImpl(schemaName, tableName, alias), + NEW: newActorTableImpl("", "new", ""), + } +} + +func newActorTableImpl(schemaName, tableName, alias string) actorTable { var ( ActorIDColumn = mysql.IntegerColumn("actor_id") FirstNameColumn = mysql.StringColumn("first_name") @@ -281,7 +294,7 @@ func newActorTable(schemaName, tableName, alias string) ActorTable { mutableColumns = mysql.ColumnList{FirstNameColumn, LastNameColumn, LastUpdateColumn} ) - return ActorTable{ + return actorTable{ Table: mysql.NewTable(schemaName, tableName, alias, allColumns...), //Columns @@ -334,7 +347,7 @@ import ( var ActorInfo = newActorInfoTable("dvds", "actor_info", "") -type ActorInfoTable struct { +type actorInfoTable struct { mysql.Table //Columns @@ -347,27 +360,40 @@ type ActorInfoTable struct { MutableColumns mysql.ColumnList } +type ActorInfoTable struct { + actorInfoTable + + NEW actorInfoTable +} + // AS creates new ActorInfoTable with assigned alias -func (a ActorInfoTable) AS(alias string) ActorInfoTable { +func (a ActorInfoTable) AS(alias string) *ActorInfoTable { return newActorInfoTable(a.SchemaName(), a.TableName(), alias) } // Schema creates new ActorInfoTable with assigned schema name -func (a ActorInfoTable) FromSchema(schemaName string) ActorInfoTable { +func (a ActorInfoTable) FromSchema(schemaName string) *ActorInfoTable { return newActorInfoTable(schemaName, a.TableName(), a.Alias()) } // WithPrefix creates new ActorInfoTable with assigned table prefix -func (a ActorInfoTable) WithPrefix(prefix string) ActorInfoTable { +func (a ActorInfoTable) WithPrefix(prefix string) *ActorInfoTable { return newActorInfoTable(a.SchemaName(), prefix+a.TableName(), a.TableName()) } // WithSuffix creates new ActorInfoTable with assigned table suffix -func (a ActorInfoTable) WithSuffix(suffix string) ActorInfoTable { +func (a ActorInfoTable) WithSuffix(suffix string) *ActorInfoTable { return newActorInfoTable(a.SchemaName(), a.TableName()+suffix, a.TableName()) } -func newActorInfoTable(schemaName, tableName, alias string) ActorInfoTable { +func newActorInfoTable(schemaName, tableName, alias string) *ActorInfoTable { + return &ActorInfoTable{ + actorInfoTable: newActorInfoTableImpl(schemaName, tableName, alias), + NEW: newActorInfoTableImpl("", "new", ""), + } +} + +func newActorInfoTableImpl(schemaName, tableName, alias string) actorInfoTable { var ( ActorIDColumn = mysql.IntegerColumn("actor_id") FirstNameColumn = mysql.StringColumn("first_name") @@ -377,7 +403,7 @@ func newActorInfoTable(schemaName, tableName, alias string) ActorInfoTable { mutableColumns = mysql.ColumnList{ActorIDColumn, FirstNameColumn, LastNameColumn, FilmInfoColumn} ) - return ActorInfoTable{ + return actorInfoTable{ Table: mysql.NewTable(schemaName, tableName, alias, allColumns...), //Columns diff --git a/tests/mysql/insert_test.go b/tests/mysql/insert_test.go index 10887f5a..431862f8 100644 --- a/tests/mysql/insert_test.go +++ b/tests/mysql/insert_test.go @@ -255,7 +255,7 @@ func TestInsertOnDuplicateKey(t *testing.T) { INSERT INTO test_sample.link VALUES (?, ?, ?, DEFAULT), (?, ?, ?, DEFAULT) -ON DUPLICATE KEY UPDATE id = (id + ?), +ON DUPLICATE KEY UPDATE id = (link.id + ?), name = ?; `, randId, "http://www.postgresqltutorial.com", "PostgreSQL Tutorial", randId, "http://www.postgresqltutorial.com", "PostgreSQL Tutorial", @@ -283,6 +283,65 @@ ON DUPLICATE KEY UPDATE id = (id + ?), }) } +func TestInsertOnDuplicateKeyUpdateNEW(t *testing.T) { + skipForMariaDB(t) + + randId := rand.Int31() + + stmt := Link.INSERT(). + MODELS([]model.Link{ + { + ID: randId, + URL: "https://www.postgresqltutorial.com", + Name: "PostgreSQL Tutorial", + Description: nil, + }, + { + ID: randId, + URL: "https://www.yahoo.com", + Name: "Yahoo", + Description: testutils.StringPtr("web portal and search engine"), + }, + }).AS_NEW(). + ON_DUPLICATE_KEY_UPDATE( + Link.ID.SET(Link.ID.ADD(Int(11))), + Link.URL.SET(Link.NEW.URL), + Link.Name.SET(Link.NEW.Name), + Link.Description.SET(Link.NEW.Description), + ) + + testutils.AssertStatementSql(t, stmt, ` +INSERT INTO test_sample.link +VALUES (?, ?, ?, ?), + (?, ?, ?, ?) AS new +ON DUPLICATE KEY UPDATE id = (link.id + ?), + url = new.url, + name = new.name, + description = new.description; +`) + + testutils.ExecuteInTxAndRollback(t, db, func(tx *sql.Tx) { + _, err := stmt.Exec(tx) + require.NoError(t, err) + + stmt := SELECT(Link.AllColumns). + FROM(Link). + WHERE(Link.ID.EQ(Int32(randId + 11))) + + var dest model.Link + + err = stmt.Query(tx, &dest) + require.NoError(t, err) + + testutils.AssertDeepEqual(t, dest, model.Link{ + ID: randId + 11, + URL: "https://www.yahoo.com", + Name: "Yahoo", + Description: testutils.StringPtr("web portal and search engine"), + }) + }) +} + func TestInsertWithQueryContext(t *testing.T) { stmt := Link.INSERT(). VALUES(1100, "http://www.postgresqltutorial.com", "PostgreSQL Tutorial", DEFAULT)