From b9548541168d54697fed015b99e732c12f2289ec Mon Sep 17 00:00:00 2001 From: Steve Ellis Date: Thu, 12 Sep 2019 10:13:59 -0400 Subject: [PATCH 01/32] bump mattn/go-sqlite3 to v1.11.0 (#2565) --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index d2424b3f..2d2fec37 100644 --- a/go.mod +++ b/go.mod @@ -9,5 +9,5 @@ require ( github.com/jinzhu/inflection v1.0.0 github.com/jinzhu/now v1.0.1 github.com/lib/pq v1.1.1 - github.com/mattn/go-sqlite3 v1.10.0 + github.com/mattn/go-sqlite3 v1.11.0 ) diff --git a/go.sum b/go.sum index d9d073e6..c43559bf 100644 --- a/go.sum +++ b/go.sum @@ -52,8 +52,8 @@ github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxv github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/lib/pq v1.1.1 h1:sJZmqHoEaY7f+NPP8pgLB/WxulyR3fewgCM2qaSlBb4= github.com/lib/pq v1.1.1/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= -github.com/mattn/go-sqlite3 v1.10.0 h1:jbhqpg7tQe4SupckyijYiy0mJJ/pRyHvXf7JdWK860o= -github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= +github.com/mattn/go-sqlite3 v1.11.0 h1:LDdKkqtYlom37fkvqs8rMPFKAMe8+SgjbwZ6ex1/A/Q= +github.com/mattn/go-sqlite3 v1.11.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= From d5cafb5db15c1c6026005bfe0b41220cf2513887 Mon Sep 17 00:00:00 2001 From: Shunsuke Otani Date: Thu, 12 Sep 2019 23:16:05 +0900 Subject: [PATCH 02/32] Fix CallbackProcessor.Get() for removed or replaced same name callback (#2548) --- callback.go | 10 +++++++--- callbacks_test.go | 48 ++++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 52 insertions(+), 6 deletions(-) diff --git a/callback.go b/callback.go index 6f60511b..202af06e 100644 --- a/callback.go +++ b/callback.go @@ -135,11 +135,15 @@ func (cp *CallbackProcessor) Replace(callbackName string, callback func(scope *S // db.Callback().Create().Get("gorm:create") func (cp *CallbackProcessor) Get(callbackName string) (callback func(scope *Scope)) { for _, p := range cp.parent.processors { - if p.name == callbackName && p.kind == cp.kind && !cp.remove { - return *p.processor + if p.name == callbackName && p.kind == cp.kind { + if p.remove { + callback = nil + } else { + callback = *p.processor + } } } - return nil + return } // getRIndex get right index from string slice diff --git a/callbacks_test.go b/callbacks_test.go index a58913d7..c1a1d5e4 100644 --- a/callbacks_test.go +++ b/callbacks_test.go @@ -2,11 +2,10 @@ package gorm_test import ( "errors" - - "github.com/jinzhu/gorm" - "reflect" "testing" + + "github.com/jinzhu/gorm" ) func (s *Product) BeforeCreate() (err error) { @@ -175,3 +174,46 @@ func TestCallbacksWithErrors(t *testing.T) { t.Errorf("Record shouldn't be deleted because of an error happened in after delete callback") } } + +func TestGetCallback(t *testing.T) { + scope := DB.NewScope(nil) + + if DB.Callback().Create().Get("gorm:test_callback") != nil { + t.Errorf("`gorm:test_callback` should be nil") + } + + DB.Callback().Create().Register("gorm:test_callback", func(scope *gorm.Scope) { scope.Set("gorm:test_callback_value", 1) }) + callback := DB.Callback().Create().Get("gorm:test_callback") + if callback == nil { + t.Errorf("`gorm:test_callback` should be non-nil") + } + callback(scope) + if v, ok := scope.Get("gorm:test_callback_value"); !ok || v != 1 { + t.Errorf("`gorm:test_callback_value` should be `1, true` but `%v, %v`", v, ok) + } + + DB.Callback().Create().Replace("gorm:test_callback", func(scope *gorm.Scope) { scope.Set("gorm:test_callback_value", 2) }) + callback = DB.Callback().Create().Get("gorm:test_callback") + if callback == nil { + t.Errorf("`gorm:test_callback` should be non-nil") + } + callback(scope) + if v, ok := scope.Get("gorm:test_callback_value"); !ok || v != 2 { + t.Errorf("`gorm:test_callback_value` should be `2, true` but `%v, %v`", v, ok) + } + + DB.Callback().Create().Remove("gorm:test_callback") + if DB.Callback().Create().Get("gorm:test_callback") != nil { + t.Errorf("`gorm:test_callback` should be nil") + } + + DB.Callback().Create().Register("gorm:test_callback", func(scope *gorm.Scope) { scope.Set("gorm:test_callback_value", 3) }) + callback = DB.Callback().Create().Get("gorm:test_callback") + if callback == nil { + t.Errorf("`gorm:test_callback` should be non-nil") + } + callback(scope) + if v, ok := scope.Get("gorm:test_callback_value"); !ok || v != 3 { + t.Errorf("`gorm:test_callback_value` should be `3, true` but `%v, %v`", v, ok) + } +} From 13f19a503687379fcf3080a49e4b2f4482355b75 Mon Sep 17 00:00:00 2001 From: Shunsuke Otani Date: Thu, 12 Sep 2019 23:16:52 +0900 Subject: [PATCH 03/32] Uncapitalize error strings (#2533) --- callback_delete.go | 2 +- callback_update.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/callback_delete.go b/callback_delete.go index 50242e48..48b97acb 100644 --- a/callback_delete.go +++ b/callback_delete.go @@ -17,7 +17,7 @@ func init() { // beforeDeleteCallback will invoke `BeforeDelete` method before deleting func beforeDeleteCallback(scope *Scope) { if scope.DB().HasBlockGlobalUpdate() && !scope.hasConditions() { - scope.Err(errors.New("Missing WHERE clause while deleting")) + scope.Err(errors.New("missing WHERE clause while deleting")) return } if !scope.HasError() { diff --git a/callback_update.go b/callback_update.go index 56711d37..699e534b 100644 --- a/callback_update.go +++ b/callback_update.go @@ -34,7 +34,7 @@ func assignUpdatingAttributesCallback(scope *Scope) { // beforeUpdateCallback will invoke `BeforeSave`, `BeforeUpdate` method before updating func beforeUpdateCallback(scope *Scope) { if scope.DB().HasBlockGlobalUpdate() && !scope.hasConditions() { - scope.Err(errors.New("Missing WHERE clause while updating")) + scope.Err(errors.New("missing WHERE clause while updating")) return } if _, ok := scope.Get("gorm:update_column"); !ok { From 0c98e7d712e2fdc3a191a7cd2a37fabfce3768f2 Mon Sep 17 00:00:00 2001 From: Christian Muehlhaeuser Date: Thu, 12 Sep 2019 16:17:31 +0200 Subject: [PATCH 04/32] Fixed import formatting to match goimports (#2568) --- dialects/postgres/postgres.go | 1 + 1 file changed, 1 insertion(+) diff --git a/dialects/postgres/postgres.go b/dialects/postgres/postgres.go index 424e8bdc..e6c088b1 100644 --- a/dialects/postgres/postgres.go +++ b/dialects/postgres/postgres.go @@ -7,6 +7,7 @@ import ( "encoding/json" "errors" "fmt" + _ "github.com/lib/pq" "github.com/lib/pq/hstore" ) From 81c17a7e2529c59efc4e74c5b32c1fb71fb12fa2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emir=20Beganovi=C4=87?= Date: Wed, 25 Sep 2019 13:22:43 +0200 Subject: [PATCH 05/32] Revert "Fix #2517 : Check for incomplete parentheses to prevent SQL injection." (#2674) This reverts commit e3cc5ea4d403078a370e299629da56cd011b6583. --- query_test.go | 17 ----------------- scope.go | 21 --------------------- 2 files changed, 38 deletions(-) diff --git a/query_test.go b/query_test.go index 2b7e0dff..15bf8b3c 100644 --- a/query_test.go +++ b/query_test.go @@ -133,23 +133,6 @@ func TestStringPrimaryKeyForNumericValueStartingWithZero(t *testing.T) { t.Errorf("Fetch a record from with a string primary key for a numeric value starting with zero should work, but failed, zip code is %v", address.ZipCode) } } -func TestStringAgainstIncompleteParentheses(t *testing.T) { - type AddressByZipCode struct { - ZipCode string `gorm:"primary_key"` - Address string - } - - DB.AutoMigrate(&AddressByZipCode{}) - DB.Create(&AddressByZipCode{ZipCode: "00502", Address: "Holtsville"}) - - var address AddressByZipCode - var addresses []AddressByZipCode - _ = DB.First(&address, "address_by_zip_codes=00502)) UNION ALL SELECT NULL,version(),current_database(),NULL,NULL,NULL,NULL,NULL--").Find(&addresses).GetErrors() - if len(addresses) > 0 { - t.Errorf("Fetch a record from with a string that has incomplete parentheses should be fail, zip code is %v", address.ZipCode) - } - -} func TestFindAsSliceOfPointers(t *testing.T) { DB.Save(&User{Name: "user"}) diff --git a/scope.go b/scope.go index 541fe522..c962c165 100644 --- a/scope.go +++ b/scope.go @@ -277,23 +277,6 @@ func (scope *Scope) AddToVars(value interface{}) string { return scope.Dialect().BindVar(len(scope.SQLVars)) } -// IsCompleteParentheses check if the string has complete parentheses to prevent SQL injection -func (scope *Scope) IsCompleteParentheses(value string) bool { - count := 0 - for i, _ := range value { - if value[i] == 40 { // ( - count++ - } else if value[i] == 41 { // ) - count-- - } - if count < 0 { - break - } - i++ - } - return count == 0 -} - // SelectAttrs return selected attributes func (scope *Scope) SelectAttrs() []string { if scope.selectAttrs == nil { @@ -573,10 +556,6 @@ func (scope *Scope) buildCondition(clause map[string]interface{}, include bool) } if value != "" { - if !scope.IsCompleteParentheses(value) { - scope.Err(fmt.Errorf("incomplete parentheses found: %v", value)) - return - } if !include { if comparisonRegexp.MatchString(value) { str = fmt.Sprintf("NOT (%v)", value) From e5d0267c0bee4a92af603ea570fa9121e6440b11 Mon Sep 17 00:00:00 2001 From: Jay Chung Date: Sat, 5 Oct 2019 12:12:47 +0800 Subject: [PATCH 06/32] Fix typo of example code --- callback.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/callback.go b/callback.go index 202af06e..719b0a78 100644 --- a/callback.go +++ b/callback.go @@ -119,8 +119,8 @@ func (cp *CallbackProcessor) Remove(callbackName string) { // Replace a registered callback with new callback // db.Callback().Create().Replace("gorm:update_time_stamp_when_create", func(*Scope) { -// scope.SetColumn("Created", now) -// scope.SetColumn("Updated", now) +// scope.SetColumn("CreatedAt", now) +// scope.SetColumn("UpdatedAt", now) // }) func (cp *CallbackProcessor) Replace(callbackName string, callback func(scope *Scope)) { cp.logger.Print(fmt.Sprintf("[info] replacing callback `%v` from %v\n", callbackName, fileWithLineNum())) From 820b5f244abf7ef16f362de39b19adfef31fff2d Mon Sep 17 00:00:00 2001 From: Alex Stockwell Date: Thu, 17 Oct 2019 07:54:11 -0700 Subject: [PATCH 07/32] MSSQL Create() fix: Add LastInsertIDReturningSuffix to dialect (#2690) * MSSQL Create() fix: Add LastInsertIDReturningSuffix to dialect Per https://github.com/denisenkom/go-mssqldb/issues/355 * MSSQL Create() fix: Added OUTPUT query to Create() builder --- callback_create.go | 47 +++++++++++++++++++++++++++++------------ dialect.go | 2 ++ dialect_common.go | 4 ++++ dialect_postgres.go | 4 ++++ dialects/mssql/mssql.go | 8 +++++++ 5 files changed, 52 insertions(+), 13 deletions(-) diff --git a/callback_create.go b/callback_create.go index 87aba8ee..3527858b 100644 --- a/callback_create.go +++ b/callback_create.go @@ -101,10 +101,11 @@ func createCallback(scope *Scope) { } lastInsertIDReturningSuffix := scope.Dialect().LastInsertIDReturningSuffix(quotedTableName, returningColumn) + lastInsertIDOutputInterstitial := scope.Dialect().LastInsertIDOutputInterstitial(quotedTableName, returningColumn, columns) if len(columns) == 0 { scope.Raw(fmt.Sprintf( - "INSERT %v INTO %v %v%v%v", + "INSERT%v INTO %v %v%v%v", addExtraSpaceIfExist(insertModifier), quotedTableName, scope.Dialect().DefaultValueStr(), @@ -113,18 +114,19 @@ func createCallback(scope *Scope) { )) } else { scope.Raw(fmt.Sprintf( - "INSERT %v INTO %v (%v) VALUES (%v)%v%v", + "INSERT%v INTO %v (%v)%v VALUES (%v)%v%v", addExtraSpaceIfExist(insertModifier), scope.QuotedTableName(), strings.Join(columns, ","), + addExtraSpaceIfExist(lastInsertIDOutputInterstitial), strings.Join(placeholders, ","), addExtraSpaceIfExist(extraOption), addExtraSpaceIfExist(lastInsertIDReturningSuffix), )) } - // execute create sql - if lastInsertIDReturningSuffix == "" || primaryField == nil { + // execute create sql: no primaryField + if primaryField == nil { if result, err := scope.SQLDB().Exec(scope.SQL, scope.SQLVars...); scope.Err(err) == nil { // set rows affected count scope.db.RowsAffected, _ = result.RowsAffected() @@ -136,16 +138,35 @@ func createCallback(scope *Scope) { } } } - } else { - if primaryField.Field.CanAddr() { - if err := scope.SQLDB().QueryRow(scope.SQL, scope.SQLVars...).Scan(primaryField.Field.Addr().Interface()); scope.Err(err) == nil { - primaryField.IsBlank = false - scope.db.RowsAffected = 1 - } - } else { - scope.Err(ErrUnaddressable) - } + return } + + // execute create sql: lastInsertID implemention for majority of dialects + if lastInsertIDReturningSuffix == "" && lastInsertIDOutputInterstitial == "" { + if result, err := scope.SQLDB().Exec(scope.SQL, scope.SQLVars...); scope.Err(err) == nil { + // set rows affected count + scope.db.RowsAffected, _ = result.RowsAffected() + + // set primary value to primary field + if primaryField != nil && primaryField.IsBlank { + if primaryValue, err := result.LastInsertId(); scope.Err(err) == nil { + scope.Err(primaryField.Set(primaryValue)) + } + } + } + return + } + + // execute create sql: dialects with additional lastInsertID requirements (currently postgres & mssql) + if primaryField.Field.CanAddr() { + if err := scope.SQLDB().QueryRow(scope.SQL, scope.SQLVars...).Scan(primaryField.Field.Addr().Interface()); scope.Err(err) == nil { + primaryField.IsBlank = false + scope.db.RowsAffected = 1 + } + } else { + scope.Err(ErrUnaddressable) + } + return } } diff --git a/dialect.go b/dialect.go index 831c0a8e..b6f95df7 100644 --- a/dialect.go +++ b/dialect.go @@ -40,6 +40,8 @@ type Dialect interface { LimitAndOffsetSQL(limit, offset interface{}) string // SelectFromDummyTable return select values, for most dbs, `SELECT values` just works, mysql needs `SELECT value FROM DUAL` SelectFromDummyTable() string + // LastInsertIDOutputInterstitial most dbs support LastInsertId, but mssql needs to use `OUTPUT` + LastInsertIDOutputInterstitial(tableName, columnName string, columns []string) string // LastInsertIdReturningSuffix most dbs support LastInsertId, but postgres needs to use `RETURNING` LastInsertIDReturningSuffix(tableName, columnName string) string // DefaultValueStr diff --git a/dialect_common.go b/dialect_common.go index e3a5b702..16da76dc 100644 --- a/dialect_common.go +++ b/dialect_common.go @@ -157,6 +157,10 @@ func (commonDialect) SelectFromDummyTable() string { return "" } +func (commonDialect) LastInsertIDOutputInterstitial(tableName, columnName string, columns []string) string { + return "" +} + func (commonDialect) LastInsertIDReturningSuffix(tableName, columnName string) string { return "" } diff --git a/dialect_postgres.go b/dialect_postgres.go index 53d31388..d2df3131 100644 --- a/dialect_postgres.go +++ b/dialect_postgres.go @@ -120,6 +120,10 @@ func (s postgres) CurrentDatabase() (name string) { return } +func (s postgres) LastInsertIDOutputInterstitial(tableName, key string, columns []string) string { + return "" +} + func (s postgres) LastInsertIDReturningSuffix(tableName, key string) string { return fmt.Sprintf("RETURNING %v.%v", tableName, key) } diff --git a/dialects/mssql/mssql.go b/dialects/mssql/mssql.go index 8c2360fc..eb79f7e7 100644 --- a/dialects/mssql/mssql.go +++ b/dialects/mssql/mssql.go @@ -190,6 +190,14 @@ func (mssql) SelectFromDummyTable() string { return "" } +func (mssql) LastInsertIDOutputInterstitial(tableName, columnName string, columns []string) string { + if len(columns) == 0 { + // No OUTPUT to query + return "" + } + return fmt.Sprintf("OUTPUT Inserted.%v", columnName) +} + func (mssql) LastInsertIDReturningSuffix(tableName, columnName string) string { return "" } From d2007b3c826bf2f528d8dae0913f77cbac3ef7fd Mon Sep 17 00:00:00 2001 From: Devin Samarin Date: Thu, 17 Oct 2019 07:56:19 -0700 Subject: [PATCH 08/32] Describe name of field for invalid SQL datatypes (#2689) --- dialect_mysql.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dialect_mysql.go b/dialect_mysql.go index 5a1ad708..1addaf36 100644 --- a/dialect_mysql.go +++ b/dialect_mysql.go @@ -120,7 +120,7 @@ func (s *mysql) DataTypeOf(field *StructField) string { } if sqlType == "" { - panic(fmt.Sprintf("invalid sql type %s (%s) for mysql", dataValue.Type().Name(), dataValue.Kind().String())) + panic(fmt.Sprintf("invalid sql type %s (%s) in field %s for mysql", dataValue.Type().Name(), dataValue.Kind().String(), field.Name)) } if strings.TrimSpace(additionalType) == "" { From 7729627ff65324940367a4ea9d068767ac4e79fb Mon Sep 17 00:00:00 2001 From: Lilit Date: Thu, 17 Oct 2019 18:12:01 +0300 Subject: [PATCH 09/32] Fix logging callbacks (#2652) --- callback.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/callback.go b/callback.go index 719b0a78..4d8e72c0 100644 --- a/callback.go +++ b/callback.go @@ -96,7 +96,7 @@ func (cp *CallbackProcessor) Before(callbackName string) *CallbackProcessor { func (cp *CallbackProcessor) Register(callbackName string, callback func(scope *Scope)) { if cp.kind == "row_query" { if cp.before == "" && cp.after == "" && callbackName != "gorm:row_query" { - cp.logger.Print(fmt.Sprintf("Registering RowQuery callback %v without specify order with Before(), After(), applying Before('gorm:row_query') by default for compatibility...\n", callbackName)) + cp.logger.Print("info", fmt.Sprintf("Registering RowQuery callback %v without specify order with Before(), After(), applying Before('gorm:row_query') by default for compatibility...", callbackName)) cp.before = "gorm:row_query" } } @@ -110,7 +110,7 @@ func (cp *CallbackProcessor) Register(callbackName string, callback func(scope * // Remove a registered callback // db.Callback().Create().Remove("gorm:update_time_stamp_when_create") func (cp *CallbackProcessor) Remove(callbackName string) { - cp.logger.Print(fmt.Sprintf("[info] removing callback `%v` from %v\n", callbackName, fileWithLineNum())) + cp.logger.Print("info", fmt.Sprintf("[info] removing callback `%v` from %v", callbackName, fileWithLineNum())) cp.name = callbackName cp.remove = true cp.parent.processors = append(cp.parent.processors, cp) @@ -123,7 +123,7 @@ func (cp *CallbackProcessor) Remove(callbackName string) { // scope.SetColumn("UpdatedAt", now) // }) func (cp *CallbackProcessor) Replace(callbackName string, callback func(scope *Scope)) { - cp.logger.Print(fmt.Sprintf("[info] replacing callback `%v` from %v\n", callbackName, fileWithLineNum())) + cp.logger.Print("info", fmt.Sprintf("[info] replacing callback `%v` from %v", callbackName, fileWithLineNum())) cp.name = callbackName cp.processor = &callback cp.replace = true @@ -166,7 +166,7 @@ func sortProcessors(cps []*CallbackProcessor) []*func(scope *Scope) { for _, cp := range cps { // show warning message the callback name already exists if index := getRIndex(allNames, cp.name); index > -1 && !cp.replace && !cp.remove { - cp.logger.Print(fmt.Sprintf("[warning] duplicated callback `%v` from %v\n", cp.name, fileWithLineNum())) + cp.logger.Print("warning", fmt.Sprintf("[warning] duplicated callback `%v` from %v", cp.name, fileWithLineNum())) } allNames = append(allNames, cp.name) } From 120d39b4d6873cb2a5a4b789a031bd2cc8465a12 Mon Sep 17 00:00:00 2001 From: okhowang <3352585+okhowang@users.noreply.github.com> Date: Thu, 17 Oct 2019 23:22:13 +0800 Subject: [PATCH 10/32] use show statement in mysql dialect for compatibility for tencent tdsql (#2643) --- dialect_mysql.go | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/dialect_mysql.go b/dialect_mysql.go index 1addaf36..ac9b3b2e 100644 --- a/dialect_mysql.go +++ b/dialect_mysql.go @@ -2,6 +2,7 @@ package gorm import ( "crypto/sha1" + "database/sql" "fmt" "reflect" "regexp" @@ -161,6 +162,39 @@ func (s mysql) HasForeignKey(tableName string, foreignKeyName string) bool { return count > 0 } +func (s mysql) HasTable(tableName string) bool { + currentDatabase, tableName := currentDatabaseAndTable(&s, tableName) + var name string + if err := s.db.QueryRow(fmt.Sprintf("SHOW TABLES FROM %s WHERE Tables_in_%s = ?", currentDatabase, currentDatabase), tableName).Scan(&name); err != nil { + if err == sql.ErrNoRows { + return false + } + panic(err) + } else { + return true + } +} + +func (s mysql) HasIndex(tableName string, indexName string) bool { + currentDatabase, tableName := currentDatabaseAndTable(&s, tableName) + if rows, err := s.db.Query(fmt.Sprintf("SHOW INDEXES FROM `%s` FROM `%s` WHERE Key_name = ?", tableName, currentDatabase), indexName); err != nil { + panic(err) + } else { + defer rows.Close() + return rows.Next() + } +} + +func (s mysql) HasColumn(tableName string, columnName string) bool { + currentDatabase, tableName := currentDatabaseAndTable(&s, tableName) + if rows, err := s.db.Query(fmt.Sprintf("SHOW COLUMNS FROM `%s` FROM `%s` WHERE Field = ?", tableName, currentDatabase), columnName); err != nil { + panic(err) + } else { + defer rows.Close() + return rows.Next() + } +} + func (s mysql) CurrentDatabase() (name string) { s.db.QueryRow("SELECT DATABASE()").Scan(&name) return From b99f2d827067caef22fdd72c967f597515fba15d Mon Sep 17 00:00:00 2001 From: "lotus.wu" Date: Thu, 17 Oct 2019 23:36:06 +0800 Subject: [PATCH 11/32] 1. suport date time '2070-03-30 00:00:00',timestamp can't support large date time. (#1823) --- dialect_mysql.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/dialect_mysql.go b/dialect_mysql.go index ac9b3b2e..da46d586 100644 --- a/dialect_mysql.go +++ b/dialect_mysql.go @@ -103,10 +103,10 @@ func (s *mysql) DataTypeOf(field *StructField) string { precision = fmt.Sprintf("(%s)", p) } - if _, ok := field.TagSettingsGet("NOT NULL"); ok || field.IsPrimaryKey { - sqlType = fmt.Sprintf("timestamp%v", precision) + if _, ok := field.TagSettings["NOT NULL"]; ok || field.IsPrimaryKey { + sqlType = fmt.Sprintf("DATETIME%v", precision) } else { - sqlType = fmt.Sprintf("timestamp%v NULL", precision) + sqlType = fmt.Sprintf("DATETIME%v NULL", precision) } } default: From a8a530db5a78f0c5719f3ea8b0970de637245da5 Mon Sep 17 00:00:00 2001 From: aimuz Date: Thu, 17 Oct 2019 23:38:37 +0800 Subject: [PATCH 12/32] SetColumn No fields ignored were processed (#2579) --- scope.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scope.go b/scope.go index c962c165..e64a8ba8 100644 --- a/scope.go +++ b/scope.go @@ -225,7 +225,7 @@ func (scope *Scope) SetColumn(column interface{}, value interface{}) error { updateAttrs[field.DBName] = value return field.Set(value) } - if (field.DBName == dbName) || (field.Name == name && mostMatchedField == nil) { + if !field.IsIgnored && ((field.DBName == dbName) || (field.Name == name && mostMatchedField == nil)) { mostMatchedField = field } } From 5b3e40ac12c1b5ad09fbcefc06fa6d7bda415ef3 Mon Sep 17 00:00:00 2001 From: macklin-10x <53452532+macklin-10x@users.noreply.github.com> Date: Thu, 17 Oct 2019 08:44:34 -0700 Subject: [PATCH 13/32] Rename expr type to make it public. (#2604) --- main.go | 6 +++--- scope.go | 6 +++--- search.go | 2 +- utils.go | 6 +++--- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/main.go b/main.go index 67e5f58e..eac28f8a 100644 --- a/main.go +++ b/main.go @@ -209,8 +209,8 @@ func (s *DB) NewScope(value interface{}) *Scope { return scope } -// QueryExpr returns the query as expr object -func (s *DB) QueryExpr() *expr { +// QueryExpr returns the query as SqlExpr object +func (s *DB) QueryExpr() *SqlExpr { scope := s.NewScope(s.Value) scope.InstanceSet("skip_bindvar", true) scope.prepareQuerySQL() @@ -219,7 +219,7 @@ func (s *DB) QueryExpr() *expr { } // SubQuery returns the query as sub query -func (s *DB) SubQuery() *expr { +func (s *DB) SubQuery() *SqlExpr { scope := s.NewScope(s.Value) scope.InstanceSet("skip_bindvar", true) scope.prepareQuerySQL() diff --git a/scope.go b/scope.go index e64a8ba8..eb7525b8 100644 --- a/scope.go +++ b/scope.go @@ -257,7 +257,7 @@ func (scope *Scope) CallMethod(methodName string) { func (scope *Scope) AddToVars(value interface{}) string { _, skipBindVar := scope.InstanceGet("skip_bindvar") - if expr, ok := value.(*expr); ok { + if expr, ok := value.(*SqlExpr); ok { exp := expr.expr for _, arg := range expr.args { if skipBindVar { @@ -785,7 +785,7 @@ func (scope *Scope) orderSQL() string { for _, order := range scope.Search.orders { if str, ok := order.(string); ok { orders = append(orders, scope.quoteIfPossible(str)) - } else if expr, ok := order.(*expr); ok { + } else if expr, ok := order.(*SqlExpr); ok { exp := expr.expr for _, arg := range expr.args { exp = strings.Replace(exp, "?", scope.AddToVars(arg), 1) @@ -912,7 +912,7 @@ func (scope *Scope) updatedAttrsWithValues(value interface{}) (results map[strin for key, value := range convertInterfaceToMap(value, true, scope.db) { if field, ok := scope.FieldByName(key); ok && scope.changeableField(field) { - if _, ok := value.(*expr); ok { + if _, ok := value.(*SqlExpr); ok { hasUpdate = true results[field.DBName] = value } else { diff --git a/search.go b/search.go index 90138595..7c4cc184 100644 --- a/search.go +++ b/search.go @@ -98,7 +98,7 @@ func (s *search) Group(query string) *search { } func (s *search) Having(query interface{}, values ...interface{}) *search { - if val, ok := query.(*expr); ok { + if val, ok := query.(*SqlExpr); ok { s.havingConditions = append(s.havingConditions, map[string]interface{}{"query": val.expr, "args": val.args}) } else { s.havingConditions = append(s.havingConditions, map[string]interface{}{"query": query, "args": values}) diff --git a/utils.go b/utils.go index e58e57a5..d2ae9465 100644 --- a/utils.go +++ b/utils.go @@ -58,15 +58,15 @@ func newSafeMap() *safeMap { } // SQL expression -type expr struct { +type SqlExpr struct { expr string args []interface{} } // Expr generate raw SQL expression, for example: // DB.Model(&product).Update("price", gorm.Expr("price * ? + ?", 2, 100)) -func Expr(expression string, args ...interface{}) *expr { - return &expr{expr: expression, args: args} +func Expr(expression string, args ...interface{}) *SqlExpr { + return &SqlExpr{expr: expression, args: args} } func indirect(reflectValue reflect.Value) reflect.Value { From 5fe32d593fad1bd8005c5fbc90489c9174ce73d6 Mon Sep 17 00:00:00 2001 From: Jinzhu Date: Mon, 21 Oct 2019 20:20:38 +0800 Subject: [PATCH 14/32] Escape table name for mysql HasTable --- dialect_mysql.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dialect_mysql.go b/dialect_mysql.go index da46d586..ee9a43d3 100644 --- a/dialect_mysql.go +++ b/dialect_mysql.go @@ -165,7 +165,7 @@ func (s mysql) HasForeignKey(tableName string, foreignKeyName string) bool { func (s mysql) HasTable(tableName string) bool { currentDatabase, tableName := currentDatabaseAndTable(&s, tableName) var name string - if err := s.db.QueryRow(fmt.Sprintf("SHOW TABLES FROM %s WHERE Tables_in_%s = ?", currentDatabase, currentDatabase), tableName).Scan(&name); err != nil { + if err := s.db.QueryRow(fmt.Sprintf("SHOW TABLES FROM `%s` WHERE `Tables_in_%s` = ?", currentDatabase, currentDatabase), tableName).Scan(&name); err != nil { if err == sql.ErrNoRows { return false } From 795328fedc12a34cd2ea7483b2d8ee618bca46c6 Mon Sep 17 00:00:00 2001 From: FWangZil <779158078@qq.com> Date: Mon, 21 Oct 2019 20:45:38 +0800 Subject: [PATCH 15/32] fix(HasTable): database name (#2717) * fix(HasTable): database name allow mysql database name with '-' character * docs: add comment --- dialect_mysql.go | 1 + 1 file changed, 1 insertion(+) diff --git a/dialect_mysql.go b/dialect_mysql.go index ee9a43d3..ab6a8a91 100644 --- a/dialect_mysql.go +++ b/dialect_mysql.go @@ -165,6 +165,7 @@ func (s mysql) HasForeignKey(tableName string, foreignKeyName string) bool { func (s mysql) HasTable(tableName string) bool { currentDatabase, tableName := currentDatabaseAndTable(&s, tableName) var name string + // allow mysql database name with '-' character if err := s.db.QueryRow(fmt.Sprintf("SHOW TABLES FROM `%s` WHERE `Tables_in_%s` = ?", currentDatabase, currentDatabase), tableName).Scan(&name); err != nil { if err == sql.ErrNoRows { return false From 530711e724f3d4c678abc73f84be52c807e3df69 Mon Sep 17 00:00:00 2001 From: Ruben de Vries Date: Tue, 22 Oct 2019 11:27:30 +0200 Subject: [PATCH 16/32] fix a race condition on IsForeignKey that is being detected by -race sometimes. --- model_struct.go | 19 ++++++++- model_struct_test.go | 93 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 110 insertions(+), 2 deletions(-) create mode 100644 model_struct_test.go diff --git a/model_struct.go b/model_struct.go index 5234b287..d9e2e90f 100644 --- a/model_struct.go +++ b/model_struct.go @@ -17,6 +17,10 @@ var DefaultTableNameHandler = func(db *DB, defaultTableName string) string { return defaultTableName } +// lock for mutating global cached model metadata +var structsLock sync.Mutex + +// global cache of model metadata var modelStructsMap sync.Map // ModelStruct model definition @@ -419,8 +423,12 @@ func (scope *Scope) GetModelStruct() *ModelStruct { for idx, foreignKey := range foreignKeys { if foreignField := getForeignField(foreignKey, toFields); foreignField != nil { if associationField := getForeignField(associationForeignKeys[idx], modelStruct.StructFields); associationField != nil { - // source foreign keys + // mark field as foreignkey, use global lock to avoid race + structsLock.Lock() foreignField.IsForeignKey = true + structsLock.Unlock() + + // association foreign keys relationship.AssociationForeignFieldNames = append(relationship.AssociationForeignFieldNames, associationField.Name) relationship.AssociationForeignDBNames = append(relationship.AssociationForeignDBNames, associationField.DBName) @@ -523,8 +531,12 @@ func (scope *Scope) GetModelStruct() *ModelStruct { for idx, foreignKey := range foreignKeys { if foreignField := getForeignField(foreignKey, toFields); foreignField != nil { if scopeField := getForeignField(associationForeignKeys[idx], modelStruct.StructFields); scopeField != nil { + // mark field as foreignkey, use global lock to avoid race + structsLock.Lock() foreignField.IsForeignKey = true - // source foreign keys + structsLock.Unlock() + + // association foreign keys relationship.AssociationForeignFieldNames = append(relationship.AssociationForeignFieldNames, scopeField.Name) relationship.AssociationForeignDBNames = append(relationship.AssociationForeignDBNames, scopeField.DBName) @@ -582,7 +594,10 @@ func (scope *Scope) GetModelStruct() *ModelStruct { for idx, foreignKey := range foreignKeys { if foreignField := getForeignField(foreignKey, modelStruct.StructFields); foreignField != nil { if associationField := getForeignField(associationForeignKeys[idx], toFields); associationField != nil { + // mark field as foreignkey, use global lock to avoid race + structsLock.Lock() foreignField.IsForeignKey = true + structsLock.Unlock() // association foreign keys relationship.AssociationForeignFieldNames = append(relationship.AssociationForeignFieldNames, associationField.Name) diff --git a/model_struct_test.go b/model_struct_test.go new file mode 100644 index 00000000..2ae419a0 --- /dev/null +++ b/model_struct_test.go @@ -0,0 +1,93 @@ +package gorm_test + +import ( + "sync" + "testing" + + "github.com/jinzhu/gorm" +) + +type ModelA struct { + gorm.Model + Name string + + ModelCs []ModelC `gorm:"foreignkey:OtherAID"` +} + +type ModelB struct { + gorm.Model + Name string + + ModelCs []ModelC `gorm:"foreignkey:OtherBID"` +} + +type ModelC struct { + gorm.Model + Name string + + OtherAID uint64 + OtherA *ModelA `gorm:"foreignkey:OtherAID"` + OtherBID uint64 + OtherB *ModelB `gorm:"foreignkey:OtherBID"` +} + +// This test will try to cause a race condition on the model's foreignkey metadata +func TestModelStructRaceSameModel(t *testing.T) { + // use a WaitGroup to execute as much in-sync as possible + // it's more likely to hit a race condition than without + n := 32 + start := sync.WaitGroup{} + start.Add(n) + + // use another WaitGroup to know when the test is done + done := sync.WaitGroup{} + done.Add(n) + + for i := 0; i < n; i++ { + go func() { + start.Wait() + + // call GetStructFields, this had a race condition before we fixed it + DB.NewScope(&ModelA{}).GetStructFields() + + done.Done() + }() + + start.Done() + } + + done.Wait() +} + +// This test will try to cause a race condition on the model's foreignkey metadata +func TestModelStructRaceDifferentModel(t *testing.T) { + // use a WaitGroup to execute as much in-sync as possible + // it's more likely to hit a race condition than without + n := 32 + start := sync.WaitGroup{} + start.Add(n) + + // use another WaitGroup to know when the test is done + done := sync.WaitGroup{} + done.Add(n) + + for i := 0; i < n; i++ { + i := i + go func() { + start.Wait() + + // call GetStructFields, this had a race condition before we fixed it + if i%2 == 0 { + DB.NewScope(&ModelA{}).GetStructFields() + } else { + DB.NewScope(&ModelB{}).GetStructFields() + } + + done.Done() + }() + + start.Done() + } + + done.Wait() +} From d926a05bec9ab9ee6f8bc6d865c1ccdf9350c74b Mon Sep 17 00:00:00 2001 From: "kouha.shu" Date: Wed, 23 Oct 2019 10:38:05 +0900 Subject: [PATCH 17/32] add warning comment --- main.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/main.go b/main.go index eac28f8a..5dda8838 100644 --- a/main.go +++ b/main.go @@ -433,7 +433,8 @@ func (s *DB) FirstOrCreate(out interface{}, where ...interface{}) *DB { return c } -// Update update attributes with callbacks, refer: https://jinzhu.github.io/gorm/crud.html#update +// Update update attributes with callbacks. refer: https://jinzhu.github.io/gorm/crud.html#update +// WARNING when update with struct, GORM will not update fields that with zero value func (s *DB) Update(attrs ...interface{}) *DB { return s.Updates(toSearchableMap(attrs...), true) } @@ -480,6 +481,7 @@ func (s *DB) Create(value interface{}) *DB { } // Delete delete value match given conditions, if the value has primary key, then will including the primary key as condition +// WARNING If model has DeletedAt field, GORM will only set field DeletedAt's value to current time func (s *DB) Delete(value interface{}, where ...interface{}) *DB { return s.NewScope(value).inlineCondition(where...).callCallbacks(s.parent.callbacks.deletes).db } From 2ee239a4c07c9e3f9948500cf01f667e89a7986d Mon Sep 17 00:00:00 2001 From: "kouha.shu" Date: Wed, 23 Oct 2019 10:40:34 +0900 Subject: [PATCH 18/32] Update main.go --- main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main.go b/main.go index 5dda8838..e39a868a 100644 --- a/main.go +++ b/main.go @@ -433,7 +433,7 @@ func (s *DB) FirstOrCreate(out interface{}, where ...interface{}) *DB { return c } -// Update update attributes with callbacks. refer: https://jinzhu.github.io/gorm/crud.html#update +// Update update attributes with callbacks, refer: https://jinzhu.github.io/gorm/crud.html#update // WARNING when update with struct, GORM will not update fields that with zero value func (s *DB) Update(attrs ...interface{}) *DB { return s.Updates(toSearchableMap(attrs...), true) From c46c01c11689fa240dc70483f00ffb10dab9141f Mon Sep 17 00:00:00 2001 From: Dom Narducci Date: Fri, 25 Oct 2019 13:51:29 -0700 Subject: [PATCH 19/32] Log callback registration if logger exists for consistency --- callback.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/callback.go b/callback.go index 4d8e72c0..56b2064a 100644 --- a/callback.go +++ b/callback.go @@ -101,6 +101,12 @@ func (cp *CallbackProcessor) Register(callbackName string, callback func(scope * } } + if cp.logger != nil { + // note cp.logger will be nil during the default gorm callback registrations + // as they occur within init() blocks. However, any user-registered callbacks + // will happen after cp.logger exists (as the default logger or user-specified). + cp.logger.Print("info", fmt.Sprintf("[info] registering callback `%v` from %v", callbackName, fileWithLineNum())) + } cp.name = callbackName cp.processor = &callback cp.parent.processors = append(cp.parent.processors, cp) From 59408390c2dce9ca8b48fae08937213e72b24f9a Mon Sep 17 00:00:00 2001 From: Jason Lee Date: Tue, 19 Nov 2019 16:08:00 +0800 Subject: [PATCH 20/32] Add `db.Transaction` method for create Transaction block. (#2767) * Add `db.Transaction` method for create Transaction block. example: ```go func CreateAnimals(db *gorm.DB) error { db.Transaction(func(tx *gorm.DB) error { if err := tx.Create(&Animal{Name: "Giraffe"}).Error; err != nil { // return any error will rollback return err } if err := tx.Create(&Animal{Name: "Lion"}).Error; err != nil { return err } // return nil will commit return nil }) } ``` * Ensure rollback when commit has error. --- main.go | 25 ++++++++++++++++++++++ main_test.go | 60 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 85 insertions(+) diff --git a/main.go b/main.go index e39a868a..48d22c85 100644 --- a/main.go +++ b/main.go @@ -525,6 +525,31 @@ func (s *DB) Debug() *DB { return s.clone().LogMode(true) } +// Transaction start a transaction as a block, +// return error will rollback, otherwise to commit. +func (s *DB) Transaction(fc func(tx *DB) error) (err error) { + tx := s.Begin() + defer func() { + if r := recover(); r != nil { + err = fmt.Errorf("%s", r) + tx.Rollback() + return + } + }() + + err = fc(tx) + + if err == nil { + err = tx.Commit().Error + } + + // Makesure rollback when Block error or Commit error + if err != nil { + tx.Rollback() + } + return +} + // Begin begins a transaction func (s *DB) Begin() *DB { return s.BeginTx(context.Background(), &sql.TxOptions{}) diff --git a/main_test.go b/main_test.go index 68bf7419..134672b7 100644 --- a/main_test.go +++ b/main_test.go @@ -8,6 +8,7 @@ import ( "context" "database/sql" "database/sql/driver" + "errors" "fmt" "os" "path/filepath" @@ -469,6 +470,65 @@ func TestTransaction(t *testing.T) { } } +func TestTransactionWithBlock(t *testing.T) { + // rollback + err := DB.Transaction(func(tx *gorm.DB) error { + u := User{Name: "transcation"} + if err := tx.Save(&u).Error; err != nil { + t.Errorf("No error should raise") + } + + if err := tx.First(&User{}, "name = ?", "transcation").Error; err != nil { + t.Errorf("Should find saved record") + } + + return errors.New("the error message") + }) + + if err.Error() != "the error message" { + t.Errorf("Transaction return error will equal the block returns error") + } + + if err := DB.First(&User{}, "name = ?", "transcation").Error; err == nil { + t.Errorf("Should not find record after rollback") + } + + // commit + DB.Transaction(func(tx *gorm.DB) error { + u2 := User{Name: "transcation-2"} + if err := tx.Save(&u2).Error; err != nil { + t.Errorf("No error should raise") + } + + if err := tx.First(&User{}, "name = ?", "transcation-2").Error; err != nil { + t.Errorf("Should find saved record") + } + return nil + }) + + if err := DB.First(&User{}, "name = ?", "transcation-2").Error; err != nil { + t.Errorf("Should be able to find committed record") + } + + // panic will rollback + DB.Transaction(func(tx *gorm.DB) error { + u3 := User{Name: "transcation-3"} + if err := tx.Save(&u3).Error; err != nil { + t.Errorf("No error should raise") + } + + if err := tx.First(&User{}, "name = ?", "transcation-3").Error; err != nil { + t.Errorf("Should find saved record") + } + + panic("force panic") + }) + + if err := DB.First(&User{}, "name = ?", "transcation").Error; err == nil { + t.Errorf("Should not find record after panic rollback") + } +} + func TestTransaction_NoErrorOnRollbackAfterCommit(t *testing.T) { tx := DB.Begin() u := User{Name: "transcation"} From 23f6840776b08a33b8eb1394616abee31a4c9e98 Mon Sep 17 00:00:00 2001 From: zaneli Date: Thu, 31 Oct 2019 02:51:26 +0900 Subject: [PATCH 21/32] Add limit and offset parse error --- dialect.go | 2 +- dialect_common.go | 19 ++++++++++-- dialect_mysql.go | 15 ++++++--- dialects/mssql/mssql.go | 17 +++++++++-- query_test.go | 68 +++++++++++++++++++++++++++++++++++++++++ scope.go | 4 ++- 6 files changed, 113 insertions(+), 12 deletions(-) diff --git a/dialect.go b/dialect.go index b6f95df7..749587f4 100644 --- a/dialect.go +++ b/dialect.go @@ -37,7 +37,7 @@ type Dialect interface { ModifyColumn(tableName string, columnName string, typ string) error // LimitAndOffsetSQL return generated SQL with Limit and Offset, as mssql has special case - LimitAndOffsetSQL(limit, offset interface{}) string + LimitAndOffsetSQL(limit, offset interface{}) (string, error) // SelectFromDummyTable return select values, for most dbs, `SELECT values` just works, mysql needs `SELECT value FROM DUAL` SelectFromDummyTable() string // LastInsertIDOutputInterstitial most dbs support LastInsertId, but mssql needs to use `OUTPUT` diff --git a/dialect_common.go b/dialect_common.go index 16da76dc..950c1986 100644 --- a/dialect_common.go +++ b/dialect_common.go @@ -139,14 +139,23 @@ func (s commonDialect) CurrentDatabase() (name string) { return } -func (commonDialect) LimitAndOffsetSQL(limit, offset interface{}) (sql string) { +// LimitAndOffsetSQL return generated SQL with Limit and Offset +func (s commonDialect) LimitAndOffsetSQL(limit, offset interface{}) (sql string, err error) { if limit != nil { - if parsedLimit, err := strconv.ParseInt(fmt.Sprint(limit), 0, 0); err == nil && parsedLimit >= 0 { + parsedLimit, err := s.parseInt(limit) + if err != nil { + return "", err + } + if parsedLimit >= 0 { sql += fmt.Sprintf(" LIMIT %d", parsedLimit) } } if offset != nil { - if parsedOffset, err := strconv.ParseInt(fmt.Sprint(offset), 0, 0); err == nil && parsedOffset >= 0 { + parsedOffset, err := s.parseInt(offset) + if err != nil { + return "", err + } + if parsedOffset >= 0 { sql += fmt.Sprintf(" OFFSET %d", parsedOffset) } } @@ -181,6 +190,10 @@ func (commonDialect) NormalizeIndexAndColumn(indexName, columnName string) (stri return indexName, columnName } +func (commonDialect) parseInt(value interface{}) (int64, error) { + return strconv.ParseInt(fmt.Sprint(value), 0, 0) +} + // IsByteArrayOrSlice returns true of the reflected value is an array or slice func IsByteArrayOrSlice(value reflect.Value) bool { return (value.Kind() == reflect.Array || value.Kind() == reflect.Slice) && value.Type().Elem() == reflect.TypeOf(uint8(0)) diff --git a/dialect_mysql.go b/dialect_mysql.go index ab6a8a91..b4467ffa 100644 --- a/dialect_mysql.go +++ b/dialect_mysql.go @@ -6,7 +6,6 @@ import ( "fmt" "reflect" "regexp" - "strconv" "strings" "time" "unicode/utf8" @@ -140,13 +139,21 @@ func (s mysql) ModifyColumn(tableName string, columnName string, typ string) err return err } -func (s mysql) LimitAndOffsetSQL(limit, offset interface{}) (sql string) { +func (s mysql) LimitAndOffsetSQL(limit, offset interface{}) (sql string, err error) { if limit != nil { - if parsedLimit, err := strconv.ParseInt(fmt.Sprint(limit), 0, 0); err == nil && parsedLimit >= 0 { + parsedLimit, err := s.parseInt(limit) + if err != nil { + return "", err + } + if parsedLimit >= 0 { sql += fmt.Sprintf(" LIMIT %d", parsedLimit) if offset != nil { - if parsedOffset, err := strconv.ParseInt(fmt.Sprint(offset), 0, 0); err == nil && parsedOffset >= 0 { + parsedOffset, err := s.parseInt(offset) + if err != nil { + return "", err + } + if parsedOffset >= 0 { sql += fmt.Sprintf(" OFFSET %d", parsedOffset) } } diff --git a/dialects/mssql/mssql.go b/dialects/mssql/mssql.go index eb79f7e7..43acb379 100644 --- a/dialects/mssql/mssql.go +++ b/dialects/mssql/mssql.go @@ -168,14 +168,25 @@ func (s mssql) CurrentDatabase() (name string) { return } -func (mssql) LimitAndOffsetSQL(limit, offset interface{}) (sql string) { +func (mssql) LimitAndOffsetSQL(limit, offset interface{}) (sql string, err error) { + parseInt := func(value interface{}) (int64, error) { + return strconv.ParseInt(fmt.Sprint(value), 0, 0) + } if offset != nil { - if parsedOffset, err := strconv.ParseInt(fmt.Sprint(offset), 0, 0); err == nil && parsedOffset >= 0 { + parsedOffset, err := parseInt(offset) + if err != nil { + return "", err + } + if parsedOffset >= 0 { sql += fmt.Sprintf(" OFFSET %d ROWS", parsedOffset) } } if limit != nil { - if parsedLimit, err := strconv.ParseInt(fmt.Sprint(limit), 0, 0); err == nil && parsedLimit >= 0 { + parsedLimit, err := parseInt(limit) + if err != nil { + return "", err + } + if parsedLimit >= 0 { if sql == "" { // add default zero offset sql += " OFFSET 0 ROWS" diff --git a/query_test.go b/query_test.go index 15bf8b3c..a23a9e24 100644 --- a/query_test.go +++ b/query_test.go @@ -457,6 +457,74 @@ func TestOffset(t *testing.T) { } } +func TestLimitAndOffsetSQL(t *testing.T) { + user1 := User{Name: "TestLimitAndOffsetSQL1", Age: 10} + user2 := User{Name: "TestLimitAndOffsetSQL2", Age: 20} + user3 := User{Name: "TestLimitAndOffsetSQL3", Age: 30} + user4 := User{Name: "TestLimitAndOffsetSQL4", Age: 40} + user5 := User{Name: "TestLimitAndOffsetSQL5", Age: 50} + if err := DB.Save(&user1).Save(&user2).Save(&user3).Save(&user4).Save(&user5).Error; err != nil { + t.Fatal(err) + } + + tests := []struct { + name string + limit, offset interface{} + users []*User + ok bool + }{ + { + name: "OK", + limit: float64(2), + offset: float64(2), + users: []*User{ + &User{Name: "TestLimitAndOffsetSQL3", Age: 30}, + &User{Name: "TestLimitAndOffsetSQL2", Age: 20}, + }, + ok: true, + }, + { + name: "Limit parse error", + limit: float64(1000000), // 1e+06 + offset: float64(2), + ok: false, + }, + { + name: "Offset parse error", + limit: float64(2), + offset: float64(1000000), // 1e+06 + ok: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var users []*User + err := DB.Where("name LIKE ?", "TestLimitAndOffsetSQL%").Order("age desc").Limit(tt.limit).Offset(tt.offset).Find(&users).Error + if tt.ok { + if err != nil { + t.Errorf("error expected nil, but got %v", err) + } + if len(users) != len(tt.users) { + t.Errorf("users length expected %d, but got %d", len(tt.users), len(users)) + } + for i := range tt.users { + if users[i].Name != tt.users[i].Name { + t.Errorf("users[%d] name expected %s, but got %s", i, tt.users[i].Name, users[i].Name) + } + if users[i].Age != tt.users[i].Age { + t.Errorf("users[%d] age expected %d, but got %d", i, tt.users[i].Age, users[i].Age) + } + } + } else { + if err == nil { + t.Error("error expected not nil, but got nil") + } + } + }) + } +} + func TestOr(t *testing.T) { user1 := User{Name: "OrUser1", Age: 1} user2 := User{Name: "OrUser2", Age: 10} diff --git a/scope.go b/scope.go index eb7525b8..0e9dfd1c 100644 --- a/scope.go +++ b/scope.go @@ -797,7 +797,9 @@ func (scope *Scope) orderSQL() string { } func (scope *Scope) limitAndOffsetSQL() string { - return scope.Dialect().LimitAndOffsetSQL(scope.Search.limit, scope.Search.offset) + sql, err := scope.Dialect().LimitAndOffsetSQL(scope.Search.limit, scope.Search.offset) + scope.Err(err) + return sql } func (scope *Scope) groupSQL() string { From 9827710b60e717b1411611da5b1bf52476aa34cb Mon Sep 17 00:00:00 2001 From: Thomas Tacquet Date: Wed, 27 Nov 2019 15:51:23 +0100 Subject: [PATCH 22/32] bump go-sqlite3 to v1.12.0 to fix go1.13 issues --- go.mod | 2 +- go.sum | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 2d2fec37..87207be4 100644 --- a/go.mod +++ b/go.mod @@ -9,5 +9,5 @@ require ( github.com/jinzhu/inflection v1.0.0 github.com/jinzhu/now v1.0.1 github.com/lib/pq v1.1.1 - github.com/mattn/go-sqlite3 v1.11.0 + github.com/mattn/go-sqlite3 v1.12.0 ) diff --git a/go.sum b/go.sum index c43559bf..9c7e8a54 100644 --- a/go.sum +++ b/go.sum @@ -54,6 +54,8 @@ github.com/lib/pq v1.1.1 h1:sJZmqHoEaY7f+NPP8pgLB/WxulyR3fewgCM2qaSlBb4= github.com/lib/pq v1.1.1/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/mattn/go-sqlite3 v1.11.0 h1:LDdKkqtYlom37fkvqs8rMPFKAMe8+SgjbwZ6ex1/A/Q= github.com/mattn/go-sqlite3 v1.11.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= +github.com/mattn/go-sqlite3 v1.12.0 h1:u/x3mp++qUxvYfulZ4HKOvVO0JWhk7HtE8lWhbGz/Do= +github.com/mattn/go-sqlite3 v1.12.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= From b543a11ca0f9768994c6be4328284b167c1d83ba Mon Sep 17 00:00:00 2001 From: Charles Strahan Date: Thu, 5 Dec 2019 03:54:32 -0600 Subject: [PATCH 23/32] transaction blocks: don't swallow panics (#2774) This improves upon #2767. Previously, the code would swallow any panics, which isn't ideal; panic is intended to be used when a critical error arises, where the process should fail fast instead of trying to limp along. This now defers the any recovery (if desired) to the client code. --- main.go | 11 ++++------- main_test.go | 29 ++++++++++++++++++++--------- 2 files changed, 24 insertions(+), 16 deletions(-) diff --git a/main.go b/main.go index 48d22c85..24fd8382 100644 --- a/main.go +++ b/main.go @@ -528,12 +528,12 @@ func (s *DB) Debug() *DB { // Transaction start a transaction as a block, // return error will rollback, otherwise to commit. func (s *DB) Transaction(fc func(tx *DB) error) (err error) { + panicked := true tx := s.Begin() defer func() { - if r := recover(); r != nil { - err = fmt.Errorf("%s", r) + // Make sure to rollback when panic, Block error or Commit error + if panicked || err != nil { tx.Rollback() - return } }() @@ -543,10 +543,7 @@ func (s *DB) Transaction(fc func(tx *DB) error) (err error) { err = tx.Commit().Error } - // Makesure rollback when Block error or Commit error - if err != nil { - tx.Rollback() - } + panicked = false return } diff --git a/main_test.go b/main_test.go index 134672b7..98ea4694 100644 --- a/main_test.go +++ b/main_test.go @@ -470,6 +470,15 @@ func TestTransaction(t *testing.T) { } } +func assertPanic(t *testing.T, f func()) { + defer func() { + if r := recover(); r == nil { + t.Errorf("The code did not panic") + } + }() + f() +} + func TestTransactionWithBlock(t *testing.T) { // rollback err := DB.Transaction(func(tx *gorm.DB) error { @@ -511,17 +520,19 @@ func TestTransactionWithBlock(t *testing.T) { } // panic will rollback - DB.Transaction(func(tx *gorm.DB) error { - u3 := User{Name: "transcation-3"} - if err := tx.Save(&u3).Error; err != nil { - t.Errorf("No error should raise") - } + assertPanic(t, func() { + DB.Transaction(func(tx *gorm.DB) error { + u3 := User{Name: "transcation-3"} + if err := tx.Save(&u3).Error; err != nil { + t.Errorf("No error should raise") + } - if err := tx.First(&User{}, "name = ?", "transcation-3").Error; err != nil { - t.Errorf("Should find saved record") - } + if err := tx.First(&User{}, "name = ?", "transcation-3").Error; err != nil { + t.Errorf("Should find saved record") + } - panic("force panic") + panic("force panic") + }) }) if err := DB.First(&User{}, "name = ?", "transcation").Error; err == nil { From 2c2fbb99e5234bd22f0659ad82104f6e9adcd63d Mon Sep 17 00:00:00 2001 From: Jinzhu Date: Thu, 5 Dec 2019 18:05:12 +0800 Subject: [PATCH 24/32] Upgrade go-sqlite to v2.0.1 --- go.mod | 2 +- go.sum | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 87207be4..4d6eb7fa 100644 --- a/go.mod +++ b/go.mod @@ -9,5 +9,5 @@ require ( github.com/jinzhu/inflection v1.0.0 github.com/jinzhu/now v1.0.1 github.com/lib/pq v1.1.1 - github.com/mattn/go-sqlite3 v1.12.0 + github.com/mattn/go-sqlite3 v2.0.1+incompatible ) diff --git a/go.sum b/go.sum index 9c7e8a54..a9ae14d5 100644 --- a/go.sum +++ b/go.sum @@ -56,6 +56,8 @@ github.com/mattn/go-sqlite3 v1.11.0 h1:LDdKkqtYlom37fkvqs8rMPFKAMe8+SgjbwZ6ex1/A github.com/mattn/go-sqlite3 v1.11.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= github.com/mattn/go-sqlite3 v1.12.0 h1:u/x3mp++qUxvYfulZ4HKOvVO0JWhk7HtE8lWhbGz/Do= github.com/mattn/go-sqlite3 v1.12.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= +github.com/mattn/go-sqlite3 v2.0.1+incompatible h1:xQ15muvnzGBHpIpdrNi1DA5x0+TcBZzsIDwmw9uTHzw= +github.com/mattn/go-sqlite3 v2.0.1+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= From 32ec5c04a6884ad3d85b6e83a77ce66de1a71816 Mon Sep 17 00:00:00 2001 From: Thomas Tacquet Date: Wed, 27 Nov 2019 15:51:23 +0100 Subject: [PATCH 25/32] bump go-sqlite3 to v1.12.0 to fix go1.13 issues --- go.mod | 2 +- go.sum | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 2d2fec37..87207be4 100644 --- a/go.mod +++ b/go.mod @@ -9,5 +9,5 @@ require ( github.com/jinzhu/inflection v1.0.0 github.com/jinzhu/now v1.0.1 github.com/lib/pq v1.1.1 - github.com/mattn/go-sqlite3 v1.11.0 + github.com/mattn/go-sqlite3 v1.12.0 ) diff --git a/go.sum b/go.sum index c43559bf..9c7e8a54 100644 --- a/go.sum +++ b/go.sum @@ -54,6 +54,8 @@ github.com/lib/pq v1.1.1 h1:sJZmqHoEaY7f+NPP8pgLB/WxulyR3fewgCM2qaSlBb4= github.com/lib/pq v1.1.1/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/mattn/go-sqlite3 v1.11.0 h1:LDdKkqtYlom37fkvqs8rMPFKAMe8+SgjbwZ6ex1/A/Q= github.com/mattn/go-sqlite3 v1.11.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= +github.com/mattn/go-sqlite3 v1.12.0 h1:u/x3mp++qUxvYfulZ4HKOvVO0JWhk7HtE8lWhbGz/Do= +github.com/mattn/go-sqlite3 v1.12.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= From 0aba7ff3a0bff05dc25ec027895b5e6789e28bb3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B4=BE=E4=B8=80=E9=A5=BC?= Date: Thu, 5 Dec 2019 18:26:16 +0800 Subject: [PATCH 26/32] Beautify callback log output (#2749) --- logger.go | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/logger.go b/logger.go index a42f2727..b4a362ce 100644 --- a/logger.go +++ b/logger.go @@ -39,6 +39,15 @@ var LogFormatter = func(values ...interface{}) (messages []interface{}) { messages = []interface{}{source, currentTime} + if len(values) == 2 { + //remove the line break + currentTime = currentTime[1:] + //remove the brackets + source = fmt.Sprintf("\033[35m%v\033[0m", values[1]) + + messages = []interface{}{currentTime, source} + } + if level == "sql" { // duration messages = append(messages, fmt.Sprintf(" \033[36;1m[%.2fms]\033[0m ", float64(values[2].(time.Duration).Nanoseconds()/1e4)/100.0)) From e8c07b55316b12d028eecac5e9a49f1b16918e44 Mon Sep 17 00:00:00 2001 From: Shunsuke Otani Date: Thu, 5 Dec 2019 23:57:15 +0900 Subject: [PATCH 27/32] Set nopLogger to DefaultCallback for avoid nil pointer dereference (#2742) --- callback.go | 9 ++------- callbacks_test.go | 30 ++++++++++++++++++++++++++++++ logger.go | 4 ++++ 3 files changed, 36 insertions(+), 7 deletions(-) diff --git a/callback.go b/callback.go index 56b2064a..1f0e3c79 100644 --- a/callback.go +++ b/callback.go @@ -3,7 +3,7 @@ package gorm import "fmt" // DefaultCallback default callbacks defined by gorm -var DefaultCallback = &Callback{} +var DefaultCallback = &Callback{logger: nopLogger{}} // Callback is a struct that contains all CRUD callbacks // Field `creates` contains callbacks will be call when creating object @@ -101,12 +101,7 @@ func (cp *CallbackProcessor) Register(callbackName string, callback func(scope * } } - if cp.logger != nil { - // note cp.logger will be nil during the default gorm callback registrations - // as they occur within init() blocks. However, any user-registered callbacks - // will happen after cp.logger exists (as the default logger or user-specified). - cp.logger.Print("info", fmt.Sprintf("[info] registering callback `%v` from %v", callbackName, fileWithLineNum())) - } + cp.logger.Print("info", fmt.Sprintf("[info] registering callback `%v` from %v", callbackName, fileWithLineNum())) cp.name = callbackName cp.processor = &callback cp.parent.processors = append(cp.parent.processors, cp) diff --git a/callbacks_test.go b/callbacks_test.go index c1a1d5e4..bebd0e38 100644 --- a/callbacks_test.go +++ b/callbacks_test.go @@ -217,3 +217,33 @@ func TestGetCallback(t *testing.T) { t.Errorf("`gorm:test_callback_value` should be `3, true` but `%v, %v`", v, ok) } } + +func TestUseDefaultCallback(t *testing.T) { + createCallbackName := "gorm:test_use_default_callback_for_create" + gorm.DefaultCallback.Create().Register(createCallbackName, func(*gorm.Scope) { + // nop + }) + if gorm.DefaultCallback.Create().Get(createCallbackName) == nil { + t.Errorf("`%s` expected non-nil, but got nil", createCallbackName) + } + gorm.DefaultCallback.Create().Remove(createCallbackName) + if gorm.DefaultCallback.Create().Get(createCallbackName) != nil { + t.Errorf("`%s` expected nil, but got non-nil", createCallbackName) + } + + updateCallbackName := "gorm:test_use_default_callback_for_update" + scopeValueName := "gorm:test_use_default_callback_for_update_value" + gorm.DefaultCallback.Update().Register(updateCallbackName, func(scope *gorm.Scope) { + scope.Set(scopeValueName, 1) + }) + gorm.DefaultCallback.Update().Replace(updateCallbackName, func(scope *gorm.Scope) { + scope.Set(scopeValueName, 2) + }) + + scope := DB.NewScope(nil) + callback := gorm.DefaultCallback.Update().Get(updateCallbackName) + callback(scope) + if v, ok := scope.Get(scopeValueName); !ok || v != 2 { + t.Errorf("`%s` should be `2, true` but `%v, %v`", scopeValueName, v, ok) + } +} diff --git a/logger.go b/logger.go index b4a362ce..88e167dd 100644 --- a/logger.go +++ b/logger.go @@ -135,3 +135,7 @@ type Logger struct { func (logger Logger) Print(values ...interface{}) { logger.Println(LogFormatter(values...)...) } + +type nopLogger struct{} + +func (nopLogger) Print(values ...interface{}) {} From 11e2819f44a6b6e2b21119a9eaf451244abd808b Mon Sep 17 00:00:00 2001 From: Jinzhu Date: Thu, 5 Dec 2019 23:13:54 +0800 Subject: [PATCH 28/32] Extract parseInt --- dialect_common.go | 12 ++++-------- dialects/mssql/mssql.go | 19 ++++++++----------- 2 files changed, 12 insertions(+), 19 deletions(-) diff --git a/dialect_common.go b/dialect_common.go index 950c1986..d549510c 100644 --- a/dialect_common.go +++ b/dialect_common.go @@ -142,20 +142,16 @@ func (s commonDialect) CurrentDatabase() (name string) { // LimitAndOffsetSQL return generated SQL with Limit and Offset func (s commonDialect) LimitAndOffsetSQL(limit, offset interface{}) (sql string, err error) { if limit != nil { - parsedLimit, err := s.parseInt(limit) - if err != nil { + if parsedLimit, err := s.parseInt(limit); err != nil { return "", err - } - if parsedLimit >= 0 { + } else if parsedLimit >= 0 { sql += fmt.Sprintf(" LIMIT %d", parsedLimit) } } if offset != nil { - parsedOffset, err := s.parseInt(offset) - if err != nil { + if parsedOffset, err := s.parseInt(offset); err != nil { return "", err - } - if parsedOffset >= 0 { + } else if parsedOffset >= 0 { sql += fmt.Sprintf(" OFFSET %d", parsedOffset) } } diff --git a/dialects/mssql/mssql.go b/dialects/mssql/mssql.go index 43acb379..cb2714e0 100644 --- a/dialects/mssql/mssql.go +++ b/dialects/mssql/mssql.go @@ -168,25 +168,22 @@ func (s mssql) CurrentDatabase() (name string) { return } +func parseInt(value interface{}) (int64, error) { + return strconv.ParseInt(fmt.Sprint(value), 0, 0) +} + func (mssql) LimitAndOffsetSQL(limit, offset interface{}) (sql string, err error) { - parseInt := func(value interface{}) (int64, error) { - return strconv.ParseInt(fmt.Sprint(value), 0, 0) - } if offset != nil { - parsedOffset, err := parseInt(offset) - if err != nil { + if parsedOffset, err := parseInt(offset); err != nil { return "", err - } - if parsedOffset >= 0 { + } else if parsedOffset >= 0 { sql += fmt.Sprintf(" OFFSET %d ROWS", parsedOffset) } } if limit != nil { - parsedLimit, err := parseInt(limit) - if err != nil { + if parsedLimit, err := parseInt(limit); err != nil { return "", err - } - if parsedLimit >= 0 { + } else if parsedLimit >= 0 { if sql == "" { // add default zero offset sql += " OFFSET 0 ROWS" From 5490a87fe9f9d72a38cfa641e7965bf48f588b87 Mon Sep 17 00:00:00 2001 From: Jinzhu Date: Fri, 6 Dec 2019 00:01:40 +0800 Subject: [PATCH 29/32] Should use global NowFunc when trace SQL --- callback_create.go | 2 +- callback_query.go | 2 +- scope.go | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/callback_create.go b/callback_create.go index 3527858b..5271dc29 100644 --- a/callback_create.go +++ b/callback_create.go @@ -50,7 +50,7 @@ func updateTimeStampForCreateCallback(scope *Scope) { // createCallback the callback used to insert data into database func createCallback(scope *Scope) { if !scope.HasError() { - defer scope.trace(scope.db.nowFunc()) + defer scope.trace(NowFunc()) var ( columns, placeholders []string diff --git a/callback_query.go b/callback_query.go index e3b3d534..7facc42b 100644 --- a/callback_query.go +++ b/callback_query.go @@ -24,7 +24,7 @@ func queryCallback(scope *Scope) { return } - defer scope.trace(scope.db.nowFunc()) + defer scope.trace(NowFunc()) var ( isSlice, isPtr bool diff --git a/scope.go b/scope.go index 0e9dfd1c..d82cadbc 100644 --- a/scope.go +++ b/scope.go @@ -358,7 +358,7 @@ func (scope *Scope) Raw(sql string) *Scope { // Exec perform generated SQL func (scope *Scope) Exec() *Scope { - defer scope.trace(scope.db.nowFunc()) + defer scope.trace(NowFunc()) if !scope.HasError() { if result, err := scope.SQLDB().Exec(scope.SQL, scope.SQLVars...); scope.Err(err) == nil { @@ -934,7 +934,7 @@ func (scope *Scope) updatedAttrsWithValues(value interface{}) (results map[strin } func (scope *Scope) row() *sql.Row { - defer scope.trace(scope.db.nowFunc()) + defer scope.trace(NowFunc()) result := &RowQueryResult{} scope.InstanceSet("row_query_result", result) @@ -944,7 +944,7 @@ func (scope *Scope) row() *sql.Row { } func (scope *Scope) rows() (*sql.Rows, error) { - defer scope.trace(scope.db.nowFunc()) + defer scope.trace(NowFunc()) result := &RowsQueryResult{} scope.InstanceSet("row_query_result", result) From 9d2b65f8c9604651197b9d864500d05ddce2cc99 Mon Sep 17 00:00:00 2001 From: Dozer Date: Fri, 6 Dec 2019 09:16:51 +0800 Subject: [PATCH 30/32] add query hint support (#2351) * add query hint support * remove add extra space * add test and fix bug * fix ut * fix ut --- callback_query.go | 5 +++++ callback_row_query.go | 5 +++++ main_test.go | 24 ++++++++++++++++++++++++ 3 files changed, 34 insertions(+) diff --git a/callback_query.go b/callback_query.go index 7facc42b..544afd63 100644 --- a/callback_query.go +++ b/callback_query.go @@ -60,6 +60,11 @@ func queryCallback(scope *Scope) { if !scope.HasError() { scope.db.RowsAffected = 0 + + if str, ok := scope.Get("gorm:query_hint"); ok { + scope.SQL = fmt.Sprint(str) + scope.SQL + } + if str, ok := scope.Get("gorm:query_option"); ok { scope.SQL += addExtraSpaceIfExist(fmt.Sprint(str)) } diff --git a/callback_row_query.go b/callback_row_query.go index 687b0039..323b1605 100644 --- a/callback_row_query.go +++ b/callback_row_query.go @@ -23,6 +23,11 @@ type RowsQueryResult struct { func rowQueryCallback(scope *Scope) { if result, ok := scope.InstanceGet("row_query_result"); ok { scope.prepareQuerySQL() + + if str, ok := scope.Get("gorm:query_hint"); ok { + scope.SQL = fmt.Sprint(str) + scope.SQL + } + if str, ok := scope.Get("gorm:query_option"); ok { scope.SQL += addExtraSpaceIfExist(fmt.Sprint(str)) } diff --git a/main_test.go b/main_test.go index 98ea4694..b51fe413 100644 --- a/main_test.go +++ b/main_test.go @@ -1333,6 +1333,30 @@ func TestCountWithQueryOption(t *testing.T) { } } +func TestQueryHint1(t *testing.T) { + db := DB.New() + + _, err := db.Model(User{}).Raw("select 1").Rows() + + if err != nil { + t.Error("Unexpected error on query count with query_option") + } +} + +func TestQueryHint2(t *testing.T) { + type TestStruct struct { + ID string `gorm:"primary_key"` + Name string + } + DB.DropTable(&TestStruct{}) + DB.AutoMigrate(&TestStruct{}) + + data := TestStruct{ID: "uuid", Name: "hello"} + if err := DB.Set("gorm:query_hint", "/*master*/").Save(&data).Error; err != nil { + t.Error("Unexpected error on query count with query_option") + } +} + func TestFloatColumnPrecision(t *testing.T) { if dialect := os.Getenv("GORM_DIALECT"); dialect != "mysql" && dialect != "sqlite" { t.Skip() From f616ccd39773f0b1c6967aab3eb1de4f04dd001f Mon Sep 17 00:00:00 2001 From: misko Date: Mon, 14 Oct 2019 14:13:18 +0800 Subject: [PATCH 31/32] 1. fix bug : https://github.com/jinzhu/gorm/issues/2700 --- main.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/main.go b/main.go index 24fd8382..3db87870 100644 --- a/main.go +++ b/main.go @@ -124,7 +124,10 @@ func (s *DB) Close() error { // DB get `*sql.DB` from current connection // If the underlying database connection is not a *sql.DB, returns nil func (s *DB) DB() *sql.DB { - db, _ := s.db.(*sql.DB) + db, ok := s.db.(*sql.DB) + if !ok { + panic("can't support full GORM on currently status, maybe this is a TX instance.") + } return db } From 79a77d771dee4e4b60e9c543e8663bbc80466670 Mon Sep 17 00:00:00 2001 From: jaden <1336364665@qq.com> Date: Fri, 6 Dec 2019 22:22:28 +0800 Subject: [PATCH 32/32] go.mod: remove unnecessary dependences through upgrade go-mssqldb (#2795) * go.mod: remove unnecessary dependences through upgrade go-mssqldb $ go get -v -u github.com/denisenkom/go-mssqldb && go mod tidy -v go: finding github.com/denisenkom/go-mssqldb latest go: finding github.com/golang-sql/civil latest go: finding golang.org/x/crypto latest unused cloud.google.com/go unused gopkg.in/check.v1 unused gopkg.in/yaml.v2 * mssql: use SCOPE_IDENTITY() if OUTPUT not possible * go-mssqldb: find a up-to-date version pass test -race --- callback_create.go | 5 +- dialects/mssql/mssql.go | 3 +- go.mod | 4 +- go.sum | 122 +++------------------------------------- 4 files changed, 17 insertions(+), 117 deletions(-) diff --git a/callback_create.go b/callback_create.go index 5271dc29..c4d25f37 100644 --- a/callback_create.go +++ b/callback_create.go @@ -100,8 +100,11 @@ func createCallback(scope *Scope) { returningColumn = scope.Quote(primaryField.DBName) } - lastInsertIDReturningSuffix := scope.Dialect().LastInsertIDReturningSuffix(quotedTableName, returningColumn) lastInsertIDOutputInterstitial := scope.Dialect().LastInsertIDOutputInterstitial(quotedTableName, returningColumn, columns) + var lastInsertIDReturningSuffix string + if lastInsertIDOutputInterstitial == "" { + lastInsertIDReturningSuffix = scope.Dialect().LastInsertIDReturningSuffix(quotedTableName, returningColumn) + } if len(columns) == 0 { scope.Raw(fmt.Sprintf( diff --git a/dialects/mssql/mssql.go b/dialects/mssql/mssql.go index cb2714e0..a516ed4a 100644 --- a/dialects/mssql/mssql.go +++ b/dialects/mssql/mssql.go @@ -207,7 +207,8 @@ func (mssql) LastInsertIDOutputInterstitial(tableName, columnName string, column } func (mssql) LastInsertIDReturningSuffix(tableName, columnName string) string { - return "" + // https://stackoverflow.com/questions/5228780/how-to-get-last-inserted-id + return "; SELECT SCOPE_IDENTITY()" } func (mssql) DefaultValueStr() string { diff --git a/go.mod b/go.mod index 4d6eb7fa..6e923b9d 100644 --- a/go.mod +++ b/go.mod @@ -3,11 +3,13 @@ module github.com/jinzhu/gorm go 1.12 require ( - github.com/denisenkom/go-mssqldb v0.0.0-20190515213511-eb9f6a1743f3 + github.com/denisenkom/go-mssqldb v0.0.0-20191124224453-732737034ffd github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5 github.com/go-sql-driver/mysql v1.4.1 github.com/jinzhu/inflection v1.0.0 github.com/jinzhu/now v1.0.1 github.com/lib/pq v1.1.1 github.com/mattn/go-sqlite3 v2.0.1+incompatible + golang.org/x/crypto v0.0.0-20191205180655-e7c4368fe9dd // indirect + google.golang.org/appengine v1.4.0 // indirect ) diff --git a/go.sum b/go.sum index a9ae14d5..915b4c21 100644 --- a/go.sum +++ b/go.sum @@ -1,135 +1,29 @@ -cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.37.4 h1:glPeL3BQJsbF6aIIYfZizMwc5LTYz250bDMjttbBGAU= -cloud.google.com/go v0.37.4/go.mod h1:NHPJ89PdicEuT9hdPXMROBD91xc5uRDxsMtSB16k7hw= -github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= -github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= -github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= -github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= -github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= -github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= -github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/denisenkom/go-mssqldb v0.0.0-20190515213511-eb9f6a1743f3 h1:tkum0XDgfR0jcVVXuTsYv/erY2NnEDqwRojbxR1rBYA= -github.com/denisenkom/go-mssqldb v0.0.0-20190515213511-eb9f6a1743f3/go.mod h1:zAg7JM8CkOJ43xKXIj7eRO9kmWm/TW578qo+oDO6tuM= -github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= -github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= -github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= +github.com/denisenkom/go-mssqldb v0.0.0-20191124224453-732737034ffd h1:83Wprp6ROGeiHFAP8WJdI2RoxALQYgdllERc3N5N2DM= +github.com/denisenkom/go-mssqldb v0.0.0-20191124224453-732737034ffd/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU= github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5 h1:Yzb9+7DPaBjB8zlTR87/ElzFsnQfuHnVUVqpZZIcV5Y= github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5/go.mod h1:a2zkGnVExMxdzMo3M0Hi/3sEU+cWnZpSni0O6/Yb/P0= -github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= -github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= -github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-sql-driver/mysql v1.4.1 h1:g24URVg0OFbNUTx9qqY1IRZ9D9z3iPyi5zKhQZpNwpA= github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= -github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= -github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= -github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= -github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe h1:lXe2qZdvpiX5WZkZR4hgp4KJVfY3nMkvmwbVkpv1rVY= +github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/go-cmp v0.2.0 h1:+dTQ8DZQJz0Mb/HjFlkptS1FeQ4cWSnN941F8aEG4SQ= -github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= -github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= -github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= -github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= -github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= -github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= -github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= github.com/jinzhu/now v1.0.1 h1:HjfetcXq097iXP0uoPCdnM4Efp5/9MsM0/M+XOTeR3M= github.com/jinzhu/now v1.0.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= -github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= -github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= -github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/lib/pq v1.1.1 h1:sJZmqHoEaY7f+NPP8pgLB/WxulyR3fewgCM2qaSlBb4= github.com/lib/pq v1.1.1/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= -github.com/mattn/go-sqlite3 v1.11.0 h1:LDdKkqtYlom37fkvqs8rMPFKAMe8+SgjbwZ6ex1/A/Q= -github.com/mattn/go-sqlite3 v1.11.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= -github.com/mattn/go-sqlite3 v1.12.0 h1:u/x3mp++qUxvYfulZ4HKOvVO0JWhk7HtE8lWhbGz/Do= -github.com/mattn/go-sqlite3 v1.12.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= github.com/mattn/go-sqlite3 v2.0.1+incompatible h1:xQ15muvnzGBHpIpdrNi1DA5x0+TcBZzsIDwmw9uTHzw= github.com/mattn/go-sqlite3 v2.0.1+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= -github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= -github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= -github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= -github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw= -github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= -github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= -github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs= -github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= -github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= -github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= -github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= -github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= -github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= -github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= -github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= -go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= -golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c h1:Vj5n4GlwjmQteupaxJ9+0FNOmBrHfq7vN4btdGoDZgI= golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= -golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= -golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/crypto v0.0.0-20191205180655-e7c4368fe9dd h1:GGJVjV8waZKRHrgwvtH66z9ZGVurTD1MT0n1Bb+q4aM= +golang.org/x/crypto v0.0.0-20191205180655-e7c4368fe9dd/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= -golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk= -google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0 h1:/wp5JvzpHIxhs/dumFmF7BXTf3Z+dd4uXta4kVyO508= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190404172233-64821d5d2107/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= -google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= -gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= -gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= -gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=